Merge branch 'master' into 1.4
diff --git a/README.md b/README.md
index 50cde68..fe3db8e 100644
--- a/README.md
+++ b/README.md
@@ -111,7 +111,7 @@
     released on 2020-05-14.
 *   [Java and Android](docs/JAVA-HOWTO.md), [C++](docs/CPP-HOWTO.md),
     [Obj-C](docs/OBJC-HOWTO.md), [Go](docs/GOLANG-HOWTO.md), and
-    [Python](g3docs/PYTHON-HOWTO.md) are field tested and ready for production.
+    [Python](docs/PYTHON-HOWTO.md) are field tested and ready for production.
 *   Tink for JavaScript is in active development.
 
 ## Learn more
diff --git a/apps/paymentmethodtoken/BUILD.bazel b/apps/paymentmethodtoken/BUILD.bazel
index 6dab6a1..6cc83f0 100644
--- a/apps/paymentmethodtoken/BUILD.bazel
+++ b/apps/paymentmethodtoken/BUILD.bazel
@@ -1,81 +1,23 @@
+load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
+
 package(default_visibility = ["//visibility:public"])
 
 licenses(["notice"])
 
-load("@tink_java//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
-
-filegroup(
-    name = "srcs",
-    srcs = glob(
-        [
-            "src/main/**/*.java",
-        ],
-    ),
-)
-
-java_library(
-    name = "paymentmethodtoken",
-    srcs = [":srcs"],
-    javacopts = JAVACOPTS_OSS,
-    deps = [
-        "@tink_java//:java",
-        "@tink_java//:subtle",
-        "@maven//:com_google_http_client_google_http_client",
-        "@maven//:joda_time_joda_time",
-        "@maven//:org_json_json",
-    ],
-)
-
-java_binary(
-    name = "recipientkeygen",
-    srcs = glob([
-        "src/main/**/PaymentMethodTokenRecipientKeyGen.java",
-    ]),
-    javacopts = JAVACOPTS_OSS,
-    main_class = "com.google.crypto.tink.apps.paymentmethodtoken.PaymentMethodTokenRecipientKeyGen",
-    deps = [
-        ":paymentmethodtoken",
-        "@tink_java//:java",
-        "@tink_java//:subtle",
-    ],
-)
-
-# Maven Jars
-
-load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-
 gen_maven_jar_rules(
     name = "maven",
     doctitle = "Tink Cryptography API for Google Payment Method Token",
     root_packages = ["com.google.crypto.tink.apps.paymentmethodtoken"],
-    deps = [":paymentmethodtoken"],
-)
-
-# Tests
-
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
-java_library(
-    name = "generator_test",
-    testonly = 1,
-    srcs = glob([
-        "src/test/**/*.java",
-    ]),
     deps = [
-        ":paymentmethodtoken",
-        "@tink_java//:testonly",
-        "@maven//:com_google_http_client_google_http_client",
-        "@maven//:joda_time_joda_time",
-        "@maven//:junit_junit",
-        "@maven//:org_json_json",
-    ],
-)
-
-gen_java_test_rules(
-    test_files = glob([
-        "src/test/**/*Test.java",
-    ]),
-    deps = [
-        ":generator_test",
+        "//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/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
new file mode 100644
index 0000000..d7131c4
--- /dev/null
+++ b/apps/paymentmethodtoken/src/main/java/com/google/crypto/tink/apps/paymentmethodtoken/BUILD.bazel
@@ -0,0 +1,125 @@
+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//:org_json_json",
+        "@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//:org_json_json",
+        "@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//:org_json_json",
+        "@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//:joda_time_joda_time",
+        "@maven//:org_json_json",
+        "@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//:org_json_json",
+        "@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/test/BUILD.bazel b/apps/paymentmethodtoken/src/test/BUILD.bazel
new file mode 100644
index 0000000..3e552b2
--- /dev/null
+++ b/apps/paymentmethodtoken/src/test/BUILD.bazel
@@ -0,0 +1,44 @@
+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_http_client_google_http_client",
+        "@maven//:joda_time_joda_time",
+        "@maven//:junit_junit",
+        "@maven//:org_json_json",
+        "@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/rewardedads/BUILD.bazel b/apps/rewardedads/BUILD.bazel
index 44b0c56..6efa5c9 100644
--- a/apps/rewardedads/BUILD.bazel
+++ b/apps/rewardedads/BUILD.bazel
@@ -1,65 +1,12 @@
+load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
+
 package(default_visibility = ["//visibility:public"])
 
 licenses(["notice"])
 
-load("@tink_java//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
-
-filegroup(
-    name = "srcs",
-    srcs = glob(
-        [
-            "src/main/**/*.java",
-        ],
-    ),
-)
-
-java_library(
-    name = "rewardedads",
-    srcs = [":srcs"],
-    javacopts = JAVACOPTS_OSS,
-    deps = [
-        "@tink_java//:java",
-        "@tink_java//:subtle",
-        "@maven//:com_google_http_client_google_http_client",
-        "@maven//:org_json_json",
-    ],
-)
-
-# Maven Jars
-
-load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-
 gen_maven_jar_rules(
     name = "maven",
     doctitle = "Tink Cryptography API for Google Mobile Rewarded Video Ads SSV",
     root_packages = ["com.google.crypto.tink.apps.rewardedads"],
-    deps = [":rewardedads"],
-)
-
-# Tests
-
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
-java_library(
-    name = "generator_test",
-    testonly = 1,
-    srcs = glob([
-        "src/test/**/*.java",
-    ]),
-    deps = [
-        ":rewardedads",
-        "@tink_java//:testonly",
-        "@maven//:com_google_http_client_google_http_client",
-        "@maven//:junit_junit",
-        "@maven//:org_json_json",
-    ],
-)
-
-gen_java_test_rules(
-    test_files = glob([
-        "src/test/**/*Test.java",
-    ]),
-    deps = [
-        ":generator_test",
-    ],
+    deps = ["//rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads:rewarded_ads_verifier"],
 )
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
new file mode 100644
index 0000000..53784c5
--- /dev/null
+++ b/apps/rewardedads/src/main/java/com/google/crypto/tink/apps/rewardedads/BUILD.bazel
@@ -0,0 +1,17 @@
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+java_library(
+    name = "rewarded_ads_verifier",
+    srcs = ["RewardedAdsVerifier.java"],
+    deps = [
+        "@maven//:com_google_http_client_google_http_client",
+        "@maven//:org_json_json",
+        "@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
index 3448368..1cd1c86 100644
--- 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
@@ -65,6 +65,8 @@
  * 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();
@@ -85,13 +87,13 @@
 
   /**
    * Instance configured to talk to fetch keys from production environment (from {@link
-   * KeysDownloader#PUBLIC_KEYS_URL_PROD}).
+   * #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
-   * KeysDownloader#KEYS_URL_TEST}).
+   * #PUBLIC_KEYS_URL_TEST}).
    */
   public static final KeysDownloader KEYS_DOWNLOADER_INSTANCE_TEST =
       new KeysDownloader(DEFAULT_BACKGROUND_EXECUTOR, DEFAULT_HTTP_TRANSPORT, PUBLIC_KEYS_URL_TEST);
@@ -130,9 +132,7 @@
           "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(Charset.forName("UTF-8"));
+        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);
@@ -174,7 +174,7 @@
   /** Builder for RewardedAdsVerifier. */
   public static class Builder {
     private final List<VerifyingPublicKeysProvider> verifyingPublicKeysProviders =
-        new ArrayList<VerifyingPublicKeysProvider>();
+        new ArrayList<>();
 
     public Builder() {}
 
diff --git a/apps/rewardedads/src/test/BUILD.bazel b/apps/rewardedads/src/test/BUILD.bazel
new file mode 100644
index 0000000..796a3e0
--- /dev/null
+++ b/apps/rewardedads/src/test/BUILD.bazel
@@ -0,0 +1,35 @@
+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_http_client_google_http_client",
+        "@maven//:junit_junit",
+        "@maven//:org_json_json",
+        "@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/webpush/BUILD.bazel b/apps/webpush/BUILD.bazel
index 06218e6..8055fad 100644
--- a/apps/webpush/BUILD.bazel
+++ b/apps/webpush/BUILD.bazel
@@ -1,54 +1,17 @@
+load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
+
 package(default_visibility = ["//visibility:public"])
 
 licenses(["notice"])
 
-load("@tink_java//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
-
-java_library(
-    name = "webpush",
-    srcs = glob([
-        "src/main/**/*.java",
-    ]),
-    javacopts = JAVACOPTS_OSS,
-    deps = [
-        "@tink_java//:java",
-        "@tink_java//:subtle",
-    ],
-)
-
-# Maven Jars
-
-load("@tink_java//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-
 gen_maven_jar_rules(
     name = "maven",
     doctitle = "Tink Cryptography API for Message Encryption for Web Push (RFC 8291)",
     root_packages = ["com.google.crypto.tink.apps.webpush"],
-    deps = [":webpush"],
-)
-
-# Tests
-
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
-java_library(
-    name = "generator_test",
-    testonly = 1,
-    srcs = glob([
-        "src/test/**/*.java",
-    ]),
     deps = [
-        ":webpush",
-        "@tink_java//:testonly",
-        "@maven//:junit_junit",
-    ],
-)
-
-gen_java_test_rules(
-    test_files = glob([
-        "src/test/**/*Test.java",
-    ]),
-    deps = [
-        ":generator_test",
+        "//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/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
new file mode 100644
index 0000000..5d2afbb
--- /dev/null
+++ b/apps/webpush/src/main/java/com/google/crypto/tink/apps/webpush/BUILD.bazel
@@ -0,0 +1,44 @@
+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/test/BUILD.bazel b/apps/webpush/src/test/BUILD.bazel
new file mode 100644
index 0000000..309b3ad
--- /dev/null
+++ b/apps/webpush/src/test/BUILD.bazel
@@ -0,0 +1,38 @@
+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/WebPushHybridEncryptTest.java b/apps/webpush/src/test/java/com/google/crypto/tink/apps/webpush/WebPushHybridEncryptTest.java
index 7db5059..7b84b84 100644
--- 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
@@ -25,6 +25,7 @@
 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;
@@ -41,59 +42,60 @@
 public class WebPushHybridEncryptTest {
   @Test
   public void testEncryptDecrypt() throws Exception {
-    for (int i = 0; i < 10; i++) {
-      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();
+    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<String>();
-      Set<String> ephemeralPublicKeys = new TreeSet<String>();
-      Set<String> payloads = new TreeSet<String>();
-      int numTests = 50;
-      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());
+    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
@@ -103,101 +105,117 @@
     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 < 100; 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();
+    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 */));
-      }
+      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 */));
-    }
+    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 */));
+    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.
     }
 
-    // Test with out of range record sizes.
-    {
-      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.
-      }
+    try {
+      new WebPushHybridEncrypt.Builder()
+          .withRecordSize(WebPushConstants.CIPHERTEXT_OVERHEAD - 1)
+          .withAuthSecret(authSecret)
+          .withRecipientPublicKey(uaPublicKey)
+          .build();
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException ex) {
+      // expected.
     }
   }
 
@@ -223,3 +241,4 @@
     }
   }
 }
+
diff --git a/cc/CMakeLists.txt b/cc/CMakeLists.txt
index 32e6596..ebf68e0 100644
--- a/cc/CMakeLists.txt
+++ b/cc/CMakeLists.txt
@@ -11,6 +11,12 @@
 
 tink_module(core)
 
+# configuration settings for the build
+option(USE_ONLY_FIPS "Enables the FIPS only mode in Tink" OFF)
+if(USE_ONLY_FIPS)
+    add_definitions(-DTINK_USE_ONLY_FIPS)
+endif()
+
 # public libraries
 
 set(TINK_VERSION_H "${TINK_GENFILE_DIR}/tink/version.h")
diff --git a/cc/aead/BUILD.bazel b/cc/aead/BUILD.bazel
index a952f93..68b986a 100644
--- a/cc/aead/BUILD.bazel
+++ b/cc/aead/BUILD.bazel
@@ -68,6 +68,7 @@
         ":kms_envelope_aead_key_manager",
         ":xchacha20_poly1305_key_manager",
         "//config:config_util",
+        "//config:tink_fips",
         "//mac:mac_config",
         "//proto:config_cc_proto",
         "//util:status",
@@ -141,6 +142,8 @@
         "//:aead",
         "//:core/key_type_manager",
         "//:key_manager",
+        "//aead:cord_aead",
+        "//aead/internal:cord_aes_gcm_boringssl",
         "//proto:aes_gcm_cc_proto",
         "//proto:tink_cc_proto",
         "//subtle:aes_gcm_boringssl",
@@ -421,6 +424,8 @@
     deps = [
         ":aes_gcm_key_manager",
         "//:aead",
+        "//aead:cord_aead",
+        "//aead/internal:cord_aes_gcm_boringssl",
         "//proto:aes_gcm_cc_proto",
         "//subtle:aead_test_util",
         "//util:istream_input_stream",
diff --git a/cc/aead/CMakeLists.txt b/cc/aead/CMakeLists.txt
index 9352440..8709c95 100644
--- a/cc/aead/CMakeLists.txt
+++ b/cc/aead/CMakeLists.txt
@@ -63,6 +63,7 @@
     tink::aead::xchacha20_poly1305_key_manager
     tink::aead::aead_wrapper
     tink::config::config_util
+    tink::config::tink_fips
     tink::mac::mac_config
     tink::util::status
     tink::proto::config_cc_proto
@@ -128,6 +129,8 @@
   SRCS
     aes_gcm_key_manager.h
   DEPS
+    tink::aead::cord_aead
+    tink::aead::internal::cord_aes_gcm_boringssl
     tink::core::aead
     tink::core::key_manager
     tink::core::key_type_manager
@@ -378,6 +381,8 @@
   SRCS aes_gcm_key_manager_test.cc
   DEPS
     tink::aead::aes_gcm_key_manager
+    tink::aead::cord_aead
+    tink::aead::internal::cord_aes_gcm_boringssl
     tink::core::aead
     tink::subtle::aead_test_util
     tink::util::istream_input_stream
diff --git a/cc/aead/aead_config.cc b/cc/aead/aead_config.cc
index dc18d80..f64eaff 100644
--- a/cc/aead/aead_config.cc
+++ b/cc/aead/aead_config.cc
@@ -30,6 +30,7 @@
 #include "tink/registry.h"
 #include "tink/util/status.h"
 #include "proto/config.pb.h"
+#include "tink/config/tink_fips.h"
 
 using google::crypto::tink::RegistryConfig;
 
@@ -47,13 +48,24 @@
   auto status = MacConfig::Register();
   if (!status.ok()) return status;
 
-  // Register key managers.
+  // Register primitive wrapper.
+  status = Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>());
+  if (!status.ok()) return status;
+
+  // Register key managers which utilize the FIPS validated BoringCrypto
+  // implementations.
   status = Registry::RegisterKeyTypeManager(
       absl::make_unique<AesCtrHmacAeadKeyManager>(), true);
   if (!status.ok()) return status;
   status = Registry::RegisterKeyTypeManager(
       absl::make_unique<AesGcmKeyManager>(), true);
   if (!status.ok()) return status;
+
+  if (kUseOnlyFips) {
+    return util::OkStatus();
+  }
+
+  // Register all the other key managers.
   status = Registry::RegisterKeyTypeManager(
       absl::make_unique<AesGcmSivKeyManager>(), true);
   if (!status.ok()) return status;
@@ -70,9 +82,11 @@
       absl::make_unique<KmsEnvelopeAeadKeyManager>(), true);
   if (!status.ok()) return status;
 
-  // Register primitive wrapper.
-  return Registry::RegisterPrimitiveWrapper(absl::make_unique<AeadWrapper>());
+  return util::OkStatus();
 }
 
+
+
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/aead/aead_config_test.cc b/cc/aead/aead_config_test.cc
index 9ad96e3..61325d0 100644
--- a/cc/aead/aead_config_test.cc
+++ b/cc/aead/aead_config_test.cc
@@ -16,6 +16,8 @@
 
 #include "tink/aead/aead_config.h"
 
+#include <list>
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "tink/aead.h"
@@ -24,10 +26,12 @@
 #include "tink/config.h"
 #include "tink/keyset_handle.h"
 #include "tink/registry.h"
+#include "tink/config/tink_fips.h"
 #include "tink/util/status.h"
 #include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 
+
 namespace crypto {
 namespace tink {
 namespace {
@@ -55,6 +59,10 @@
 // Tests that the AeadWrapper has been properly registered and we can wrap
 // primitives.
 TEST_F(AeadConfigTest, WrappersRegistered) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   ASSERT_TRUE(AeadConfig::Register().ok());
 
   google::crypto::tink::Keyset::Key key;
@@ -84,6 +92,47 @@
   EXPECT_FALSE(decryption_result.status().ok());
 }
 
+// FIPS-only mode tests
+TEST_F(AeadConfigTest, RegisterNonFipsTemplates) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  EXPECT_THAT(AeadConfig::Register(), IsOk());
+
+  std::list<google::crypto::tink::KeyTemplate> non_fips_key_templates;
+  non_fips_key_templates.push_back(AeadKeyTemplates::Aes128Eax());
+  non_fips_key_templates.push_back(AeadKeyTemplates::Aes256Eax());
+  non_fips_key_templates.push_back(AeadKeyTemplates::Aes128GcmSiv());
+  non_fips_key_templates.push_back(AeadKeyTemplates::Aes256GcmSiv());
+  non_fips_key_templates.push_back(AeadKeyTemplates::XChaCha20Poly1305());
+
+  for (auto key_template : non_fips_key_templates) {
+    auto new_keyset_handle_result = KeysetHandle::GenerateNew(key_template);
+    EXPECT_THAT(new_keyset_handle_result.status(),
+                StatusIs(util::error::NOT_FOUND));
+  }
+}
+
+TEST_F(AeadConfigTest, RegisterFipsValidTemplates) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  EXPECT_THAT(AeadConfig::Register(), IsOk());
+
+  std::list<google::crypto::tink::KeyTemplate> fips_key_templates;
+  fips_key_templates.push_back(AeadKeyTemplates::Aes128Gcm());
+  fips_key_templates.push_back(AeadKeyTemplates::Aes256Gcm());
+  fips_key_templates.push_back(AeadKeyTemplates::Aes128CtrHmacSha256());
+  fips_key_templates.push_back(AeadKeyTemplates::Aes256CtrHmacSha256());
+
+  for (auto key_template : fips_key_templates) {
+    auto new_keyset_handle_result = KeysetHandle::GenerateNew(key_template);
+    EXPECT_THAT(new_keyset_handle_result.status(), IsOk());
+  }
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/aead/aes_ctr_hmac_aead_key_manager.cc b/cc/aead/aes_ctr_hmac_aead_key_manager.cc
index 7c1af3c..645b574 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager.cc
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager.cc
@@ -102,6 +102,9 @@
   Status status = ValidateVersion(key.version(), get_version());
   if (!status.ok()) return status;
 
+  status = ValidateVersion(key.aes_ctr_key().version(), get_version());
+  if (!status.ok()) return status;
+
   // Validate AesCtrKey.
   auto aes_ctr_key = key.aes_ctr_key();
   uint32_t aes_key_size = aes_ctr_key.key_value().size();
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 ddf6210..b3f72dc 100644
--- a/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
+++ b/cc/aead/aes_ctr_hmac_aead_key_manager_test.cc
@@ -208,8 +208,8 @@
       key.hmac_key().params().tag_size());
   ASSERT_THAT(direct_aead_or.status(), IsOk());
 
-  EXPECT_THAT(EncryptThenDecrypt(aead_or.ValueOrDie().get(),
-                                 direct_aead_or.ValueOrDie().get(),
+  EXPECT_THAT(EncryptThenDecrypt(*aead_or.ValueOrDie(),
+                                 *direct_aead_or.ValueOrDie(),
                                  "message", "aad"),
               IsOk());
 }
diff --git a/cc/aead/aes_eax_key_manager_test.cc b/cc/aead/aes_eax_key_manager_test.cc
index d1f8a14..fdf9695 100644
--- a/cc/aead/aes_eax_key_manager_test.cc
+++ b/cc/aead/aes_eax_key_manager_test.cc
@@ -184,8 +184,8 @@
           key_or.ValueOrDie().params().iv_size());
   ASSERT_THAT(boring_ssl_aead_or.status(), IsOk());
 
-  ASSERT_THAT(EncryptThenDecrypt(aead_or.ValueOrDie().get(),
-                                 boring_ssl_aead_or.ValueOrDie().get(),
+  ASSERT_THAT(EncryptThenDecrypt(*aead_or.ValueOrDie(),
+                                 *boring_ssl_aead_or.ValueOrDie(),
                                  "message", "aad"),
               IsOk());
 }
diff --git a/cc/aead/aes_gcm_key_manager.h b/cc/aead/aes_gcm_key_manager.h
index 94d5659..ed850b5 100644
--- a/cc/aead/aes_gcm_key_manager.h
+++ b/cc/aead/aes_gcm_key_manager.h
@@ -22,6 +22,8 @@
 #include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
+#include "tink/aead/cord_aead.h"
+#include "tink/aead/internal/cord_aes_gcm_boringssl.h"
 #include "tink/core/key_type_manager.h"
 #include "tink/key_manager.h"
 #include "tink/subtle/aes_gcm_boringssl.h"
@@ -42,7 +44,8 @@
 
 class AesGcmKeyManager
     : public KeyTypeManager<google::crypto::tink::AesGcmKey,
-                            google::crypto::tink::AesGcmKeyFormat, List<Aead>> {
+                            google::crypto::tink::AesGcmKeyFormat,
+                            List<Aead, CordAead>> {
  public:
   class AeadFactory : public PrimitiveFactory<Aead> {
     crypto::tink::util::StatusOr<std::unique_ptr<Aead>> Create(
@@ -53,9 +56,20 @@
       return {std::move(aes_gcm_result.ValueOrDie())};
     }
   };
+  class CordAeadFactory : public PrimitiveFactory<CordAead> {
+    crypto::tink::util::StatusOr<std::unique_ptr<CordAead>> Create(
+        const google::crypto::tink::AesGcmKey& key) const override {
+      auto cord_aes_gcm_result = crypto::tink::CordAesGcmBoringSsl::New(
+          util::SecretDataFromStringView(key.key_value()));
+      if (!cord_aes_gcm_result.ok()) return cord_aes_gcm_result.status();
+      return {std::move(cord_aes_gcm_result.ValueOrDie())};
+    }
+  };
 
   AesGcmKeyManager()
-      : KeyTypeManager(absl::make_unique<AesGcmKeyManager::AeadFactory>()) {}
+      : KeyTypeManager(absl::make_unique<AesGcmKeyManager::AeadFactory>(),
+                       absl::make_unique<AesGcmKeyManager::CordAeadFactory>()) {
+  }
 
   // Returns the version of this key manager.
   uint32_t get_version() const override { return 0; }
diff --git a/cc/aead/aes_gcm_key_manager_test.cc b/cc/aead/aes_gcm_key_manager_test.cc
index 458339f..397b98b 100644
--- a/cc/aead/aes_gcm_key_manager_test.cc
+++ b/cc/aead/aes_gcm_key_manager_test.cc
@@ -19,6 +19,7 @@
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "tink/aead.h"
+#include "tink/aead/internal/cord_aes_gcm_boringssl.h"
 #include "tink/subtle/aead_test_util.h"
 #include "tink/util/istream_input_stream.h"
 #include "tink/util/secret_data.h"
@@ -178,8 +179,30 @@
           util::SecretDataFromStringView(key_or.ValueOrDie().key_value()));
   ASSERT_THAT(boring_ssl_aead_or.status(), IsOk());
 
-  ASSERT_THAT(EncryptThenDecrypt(aead_or.ValueOrDie().get(),
-                                 boring_ssl_aead_or.ValueOrDie().get(),
+  ASSERT_THAT(EncryptThenDecrypt(*aead_or.ValueOrDie(),
+                                 *boring_ssl_aead_or.ValueOrDie(),
+                                 "message", "aad"),
+              IsOk());
+}
+
+TEST(AesGcmKeyManagerTest, CreateCordAead) {
+  AesGcmKeyFormat format;
+  format.set_key_size(32);
+  StatusOr<AesGcmKey> key_or = AesGcmKeyManager().CreateKey(format);
+  ASSERT_THAT(key_or.status(), IsOk());
+
+  StatusOr<std::unique_ptr<CordAead>> aead_or =
+      AesGcmKeyManager().GetPrimitive<CordAead>(key_or.ValueOrDie());
+
+  ASSERT_THAT(aead_or.status(), IsOk());
+
+  StatusOr<std::unique_ptr<CordAead>> boring_ssl_aead_or =
+      crypto::tink::CordAesGcmBoringSsl::New(
+          util::SecretDataFromStringView(key_or.ValueOrDie().key_value()));
+  ASSERT_THAT(boring_ssl_aead_or.status(), IsOk());
+
+  ASSERT_THAT(EncryptThenDecrypt(*aead_or.ValueOrDie(),
+                                 *boring_ssl_aead_or.ValueOrDie(),
                                  "message", "aad"),
               IsOk());
 }
diff --git a/cc/aead/aes_gcm_siv_key_manager_test.cc b/cc/aead/aes_gcm_siv_key_manager_test.cc
index 637b8cb..9f5a5c8 100644
--- a/cc/aead/aes_gcm_siv_key_manager_test.cc
+++ b/cc/aead/aes_gcm_siv_key_manager_test.cc
@@ -167,8 +167,8 @@
           util::SecretDataFromStringView(key_or.ValueOrDie().key_value()));
   ASSERT_THAT(boring_ssl_aead_or.status(), IsOk());
 
-  ASSERT_THAT(EncryptThenDecrypt(aead_or.ValueOrDie().get(),
-                                 boring_ssl_aead_or.ValueOrDie().get(),
+  ASSERT_THAT(EncryptThenDecrypt(*aead_or.ValueOrDie().get(),
+                                 *boring_ssl_aead_or.ValueOrDie().get(),
                                  "message", "aad"),
               IsOk());
 }
diff --git a/cc/aead/kms_aead_key_manager_test.cc b/cc/aead/kms_aead_key_manager_test.cc
index 308db71..5127ef2 100644
--- a/cc/aead/kms_aead_key_manager_test.cc
+++ b/cc/aead/kms_aead_key_manager_test.cc
@@ -123,7 +123,7 @@
 
   DummyAead direct_aead("prefix1:some_key1");
 
-  EXPECT_THAT(EncryptThenDecrypt(kms_aead.ValueOrDie().get(), &direct_aead,
+  EXPECT_THAT(EncryptThenDecrypt(*kms_aead.ValueOrDie(), direct_aead,
                                  "plaintext", "aad"),
               IsOk());
 }
@@ -156,7 +156,7 @@
 
   DummyAead direct_aead("prefix2:some_key2");
 
-  EXPECT_THAT(EncryptThenDecrypt(kms_aead.ValueOrDie().get(), &direct_aead,
+  EXPECT_THAT(EncryptThenDecrypt(*kms_aead.ValueOrDie(), direct_aead,
                                  "plaintext", "aad"),
               IsOk());
 }
diff --git a/cc/aead/kms_envelope_aead_key_manager_test.cc b/cc/aead/kms_envelope_aead_key_manager_test.cc
index 544d068..ff4202f 100644
--- a/cc/aead/kms_envelope_aead_key_manager_test.cc
+++ b/cc/aead/kms_envelope_aead_key_manager_test.cc
@@ -163,8 +163,8 @@
   ASSERT_THAT(direct_aead.status(), IsOk());
 
   EXPECT_THAT(
-      EncryptThenDecrypt(kms_aead.ValueOrDie().get(),
-                         direct_aead.ValueOrDie().get(), "plaintext", "aad"),
+      EncryptThenDecrypt(*kms_aead.ValueOrDie(),
+                         *direct_aead.ValueOrDie(), "plaintext", "aad"),
       IsOk());
 }
 
@@ -219,8 +219,8 @@
   ASSERT_THAT(direct_aead.status(), IsOk());
 
   EXPECT_THAT(
-      EncryptThenDecrypt(kms_aead.ValueOrDie().get(),
-                         direct_aead.ValueOrDie().get(), "plaintext", "aad"),
+      EncryptThenDecrypt(*kms_aead.ValueOrDie(),
+                         *direct_aead.ValueOrDie(), "plaintext", "aad"),
       IsOk());
 }
 
diff --git a/cc/aead/xchacha20_poly1305_key_manager_test.cc b/cc/aead/xchacha20_poly1305_key_manager_test.cc
index 3bce99a..07b1c46 100644
--- a/cc/aead/xchacha20_poly1305_key_manager_test.cc
+++ b/cc/aead/xchacha20_poly1305_key_manager_test.cc
@@ -128,8 +128,8 @@
   ASSERT_THAT(direct_aead_or.status(), IsOk());
 
   ASSERT_THAT(
-      EncryptThenDecrypt(aead_or.ValueOrDie().get(),
-                         direct_aead_or.ValueOrDie().get(), "message", "aad"),
+      EncryptThenDecrypt(*aead_or.ValueOrDie(),
+                         *direct_aead_or.ValueOrDie(), "message", "aad"),
       IsOk());
 }
 
diff --git a/cc/config/BUILD.bazel b/cc/config/BUILD.bazel
index dde6656..9c0faa3 100644
--- a/cc/config/BUILD.bazel
+++ b/cc/config/BUILD.bazel
@@ -32,6 +32,28 @@
     ],
 )
 
+config_setting(
+    name = "only_fips",
+    values = {"define": "use_only_fips=on"},
+)
+
+cc_library(
+    name = "tink_fips",
+    srcs = ["tink_fips.cc"],
+    hdrs = ["tink_fips.h"],
+    include_prefix = "tink/config",
+    defines = select({
+        "only_fips": ["TINK_USE_ONLY_FIPS"],
+        "//conditions:default": [],
+    }),
+    visibility = ["//visibility:public"],
+    deps = [
+        "@com_google_absl//absl/base:core_headers",
+        "@boringssl//:crypto",
+        "//util:status",
+    ],
+)
+
 # tests
 
 cc_test(
diff --git a/cc/config/CMakeLists.txt b/cc/config/CMakeLists.txt
index e7224dc..f8ce265 100644
--- a/cc/config/CMakeLists.txt
+++ b/cc/config/CMakeLists.txt
@@ -27,6 +27,17 @@
     tink::proto::config_cc_proto
 )
 
+tink_cc_library(
+  NAME tink_fips
+  SRCS
+    tink_fips.cc
+    tink_fips.h
+  DEPS
+    absl::base
+    crypto
+    tink::util::status
+)
+
 # tests
 
 tink_cc_test(
diff --git a/cc/config/tink_fips.cc b/cc/config/tink_fips.cc
new file mode 100644
index 0000000..402f527
--- /dev/null
+++ b/cc/config/tink_fips.cc
@@ -0,0 +1,50 @@
+// 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/tink_fips.h"
+
+namespace crypto {
+namespace tink {
+
+#ifdef TINK_USE_ONLY_FIPS
+const bool kUseOnlyFips = true;
+#else
+const bool kUseOnlyFips = false;
+#endif
+
+crypto::tink::util::Status ChecksFipsCompatibility(
+    FipsCompatibility fips_status) {
+  switch (fips_status) {
+    case FipsCompatibility::kNotFips:
+      if (kUseOnlyFips) {
+        return util::Status(util::error::INTERNAL,
+                            "Primitive not available in FIPS only mode");
+      } else {
+        return util::OkStatus();
+      }
+    case FipsCompatibility::kRequiresBoringCrypto:
+      if (kUseOnlyFips && !FIPS_mode()) {
+        return util::Status(
+            util::error::INTERNAL,
+            "BoringSSL not built with the BoringCrypto module. If you want to "
+            "use "
+            "FIPS only mode you have to build BoringSSL in FIPS Mode.");
+
+      } else {
+        return util::OkStatus();
+      }
+  }
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/config/tink_fips.h b/cc/config/tink_fips.h
new file mode 100644
index 0000000..8d1e8ed
--- /dev/null
+++ b/cc/config/tink_fips.h
@@ -0,0 +1,55 @@
+// 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_TINK_FIPS_H_
+#define TINK_CONFIG_TINK_FIPS_H_
+
+#include "absl/base/attributes.h"
+#include "openssl/crypto.h"
+#include "tink/util/status.h"
+
+namespace crypto {
+namespace tink {
+
+// This flag indicates whether Tink was build in FIPS only mode. If the flag
+// is set, then usage of algorithms will be restricted to algorithms which
+// utilize the FIPS validated BoringCrypto module.
+ABSL_CONST_INIT extern const bool kUseOnlyFips;
+
+// Should be used to indicate whether an algorithm can be used in FIPS only
+// mode or not.
+enum class FipsCompatibility {
+  kNotFips = 0,  // The algorithm can not use a FIPS validated implementation.
+  kRequiresBoringCrypto,  // The algorithm requires BoringCrypto to use a FIPS
+                          // validated implementation.
+};
+
+// Allows to check for a cryptographic algorithm whether it is available in
+// the FIPS only mode, based on it's FipsCompatibility flag. If FIPS only
+// mode is enabled this will return an INTERNAL error if:
+// 1) The algorithm has no FIPS support.
+// 2) The algorithm has FIPS support, but BoringSSL has not been compiled with
+//    the BoringCrypto module.
+crypto::tink::util::Status ChecksFipsCompatibility(
+    FipsCompatibility fips_status);
+
+// Utility function wich calls CheckFipsCompatibility(T::kFipsStatus).
+template <class T>
+crypto::tink::util::Status CheckFipsCompatibility() {
+  return ChecksFipsCompatibility(T::kFipsStatus);
+}
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_CONFIG_TINK_FIPS_H_
diff --git a/cc/config/tink_fips_disabled_test.cc b/cc/config/tink_fips_disabled_test.cc
new file mode 100644
index 0000000..d9877b4
--- /dev/null
+++ b/cc/config/tink_fips_disabled_test.cc
@@ -0,0 +1,48 @@
+// 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 "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/config/tink_fips.h"
+#include "tink/util/test_matchers.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using testing::Eq;
+
+TEST(TinkFipsTest, FlagCorrectlySet) { EXPECT_THAT(kUseOnlyFips, Eq(false)); }
+
+class FipsIncompatible {
+ public:
+  static constexpr crypto::tink::FipsCompatibility kFipsStatus =
+      crypto::tink::FipsCompatibility::kNotFips;
+};
+
+class FipsCompatibleWithBoringCrypto {
+ public:
+  static constexpr crypto::tink::FipsCompatibility kFipsStatus =
+      crypto::tink::FipsCompatibility::kRequiresBoringCrypto;
+};
+
+TEST(TinkFipsTest, Compatibility) {
+  // With FIPS only mode disabled no restrictions should apply.
+  EXPECT_THAT(CheckFipsCompatibility<FipsIncompatible>(), IsOk());
+  EXPECT_THAT(CheckFipsCompatibility<FipsCompatibleWithBoringCrypto>(), IsOk());
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/config/tink_fips_enabled_test.cc b/cc/config/tink_fips_enabled_test.cc
new file mode 100644
index 0000000..9562f58
--- /dev/null
+++ b/cc/config/tink_fips_enabled_test.cc
@@ -0,0 +1,81 @@
+// 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 "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "openssl/crypto.h"
+#include "tink/aead.h"
+#include "tink/aead/aead_config.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/config/tink_fips.h"
+#include "tink/keyset_handle.h"
+#include "tink/util/status.h"
+#include "tink/util/test_matchers.h"
+#include "tink/util/test_util.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+
+TEST(TinkFipsTest, FlagCorrectlySet) {
+  EXPECT_THAT(kUseOnlyFips, testing::Eq(true));
+}
+
+class FipsIncompatible {
+ public:
+  static constexpr crypto::tink::FipsCompatibility kFipsStatus =
+      crypto::tink::FipsCompatibility::kNotFips;
+};
+
+class FipsCompatibleWithBoringCrypto {
+ public:
+  static constexpr crypto::tink::FipsCompatibility kFipsStatus =
+      crypto::tink::FipsCompatibility::kRequiresBoringCrypto;
+};
+
+TEST(TinkFipsTest, CompatibilityChecksWithBoringCrypto) {
+  if (!FIPS_mode()) {
+    GTEST_SKIP() << "Test only run if BoringCrypto module is available.";
+  }
+
+  // In FIPS only mode compatibility checks should disallow algorithms
+  // with the FipsCompatibility::kNone flag.
+  EXPECT_THAT(CheckFipsCompatibility<FipsIncompatible>(),
+              StatusIs(util::error::INTERNAL));
+
+  // FIPS validated implementations should still be allowed.
+  EXPECT_THAT(CheckFipsCompatibility<FipsCompatibleWithBoringCrypto>(), IsOk());
+}
+
+TEST(TinkFipsTest, CompatibilityChecksWithoutBoringCrypto) {
+  if (FIPS_mode()) {
+    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(CheckFipsCompatibility<FipsIncompatible>(),
+              StatusIs(util::error::INTERNAL));
+
+  // FIPS validated implementations are not allowed if BoringCrypto is not
+  // available.
+  EXPECT_THAT(CheckFipsCompatibility<FipsCompatibleWithBoringCrypto>(),
+              StatusIs(util::error::INTERNAL));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/cc/core/private_key_manager_impl_test.cc b/cc/core/private_key_manager_impl_test.cc
index 4f535b0..4abe0d3 100644
--- a/cc/core/private_key_manager_impl_test.cc
+++ b/cc/core/private_key_manager_impl_test.cc
@@ -70,14 +70,14 @@
     return google::crypto::tink::KeyData::ASYMMETRIC_PRIVATE;
   }
 
-  MOCK_CONST_METHOD0(get_version, uint32_t());
+  MOCK_METHOD(uint32_t, get_version, (), (const, override));
 
   // We mock out ValidateKey and ValidateKeyFormat so that we can easily test
   // proper behavior in case they return an error.
-  MOCK_CONST_METHOD1(ValidateKey,
-                     crypto::tink::util::Status(const EcdsaPrivateKey& key));
-  MOCK_CONST_METHOD1(ValidateKeyFormat,
-                     crypto::tink::util::Status(const EcdsaKeyFormat& key));
+  MOCK_METHOD(crypto::tink::util::Status, ValidateKey,
+              (const EcdsaPrivateKey& key), (const, override));
+  MOCK_METHOD(crypto::tink::util::Status, ValidateKeyFormat,
+              (const EcdsaKeyFormat& key), (const, override));
 
   const std::string& get_key_type() const override { return kKeyType; }
 
@@ -119,12 +119,12 @@
     return google::crypto::tink::KeyData::ASYMMETRIC_PRIVATE;
   }
 
-  MOCK_CONST_METHOD0(get_version, uint32_t());
+  MOCK_METHOD(uint32_t, get_version, (), (const, override));
 
   // We mock out ValidateKey and ValidateKeyFormat so that we can easily test
   // proper behavior in case they return an error.
-  MOCK_CONST_METHOD1(ValidateKey,
-                     crypto::tink::util::Status(const EcdsaPublicKey& key));
+  MOCK_METHOD(crypto::tink::util::Status, ValidateKey,
+              (const EcdsaPublicKey& key), (const, override));
 
   const std::string& get_key_type() const override { return kKeyType; }
 
diff --git a/cc/core/registry_impl.h b/cc/core/registry_impl.h
index 58c4550..fc060e5 100644
--- a/cc/core/registry_impl.h
+++ b/cc/core/registry_impl.h
@@ -27,7 +27,6 @@
 #include "absl/strings/str_join.h"
 #include "absl/types/optional.h"
 #include "absl/synchronization/mutex.h"
-// placeholder for dllexport_macros.h
 #include "tink/catalogue.h"
 #include "tink/core/key_manager_impl.h"
 #include "tink/core/key_type_manager.h"
diff --git a/cc/core/registry_test.cc b/cc/core/registry_test.cc
index 4e56ae9..52ea4b7 100644
--- a/cc/core/registry_test.cc
+++ b/cc/core/registry_test.cc
@@ -1321,7 +1321,13 @@
                        HasSubstr("not among supported primitives")));
 }
 
-TEST(PrivateKeyManagerImplTest, AsymmetricFactoryNewKeyFromMessage) {
+class PrivateKeyManagerImplTest : public testing::Test {
+  void SetUp() override {
+    Registry::Reset();
+  }
+};
+
+TEST_F(PrivateKeyManagerImplTest, AsymmetricFactoryNewKeyFromMessage) {
   ASSERT_TRUE(Registry::RegisterAsymmetricKeyManagers(
                   absl::make_unique<TestPrivateKeyTypeManager>(),
                   absl::make_unique<TestPublicKeyTypeManager>(), true)
@@ -1343,7 +1349,7 @@
               Eq(EcdsaSignatureEncoding::DER));
 }
 
-TEST(PrivateKeyManagerImplTest, AsymmetricNewKeyDisallowed) {
+TEST_F(PrivateKeyManagerImplTest, AsymmetricNewKeyDisallowed) {
   ASSERT_TRUE(Registry::RegisterAsymmetricKeyManagers(
                   absl::make_unique<TestPrivateKeyTypeManager>(),
                   absl::make_unique<TestPublicKeyTypeManager>(), true)
diff --git a/cc/daead/BUILD.bazel b/cc/daead/BUILD.bazel
index 94aceea..e29a342 100644
--- a/cc/daead/BUILD.bazel
+++ b/cc/daead/BUILD.bazel
@@ -52,6 +52,7 @@
         ":aes_siv_key_manager",
         ":deterministic_aead_wrapper",
         "//config:config_util",
+        "//config:tink_fips",
         "//mac:mac_config",
         "//proto:config_cc_proto",
         "//util:status",
@@ -140,6 +141,7 @@
         "//:deterministic_aead",
         "//:keyset_handle",
         "//:registry",
+        "//config:tink_fips",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
diff --git a/cc/daead/CMakeLists.txt b/cc/daead/CMakeLists.txt
index bf04071..08b879e 100644
--- a/cc/daead/CMakeLists.txt
+++ b/cc/daead/CMakeLists.txt
@@ -47,6 +47,7 @@
     tink::daead::aes_siv_key_manager
     tink::daead::deterministic_aead_wrapper
     tink::config::config_util
+    tink::config::tink_fips
     tink::mac::mac_config
     tink::util::status
     tink::proto::config_cc_proto
@@ -118,6 +119,7 @@
     tink::daead::aes_siv_key_manager
     tink::daead::deterministic_aead_config
     tink::daead::deterministic_aead_key_templates
+    tink::config::tink_fips
     tink::core::config
     tink::core::deterministic_aead
     tink::core::keyset_handle
diff --git a/cc/daead/deterministic_aead_config.cc b/cc/daead/deterministic_aead_config.cc
index 4a4232b..f745f9d 100644
--- a/cc/daead/deterministic_aead_config.cc
+++ b/cc/daead/deterministic_aead_config.cc
@@ -18,6 +18,7 @@
 
 #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/deterministic_aead_wrapper.h"
 #include "tink/registry.h"
@@ -37,7 +38,13 @@
 
 // static
 util::Status DeterministicAeadConfig::Register() {
-  // Register key manager.
+  // Currently there are no FIPS-validated deterministic AEAD key managers
+  // available, therefore none will be registered in FIPS only mode.
+  if (kUseOnlyFips) {
+    return util::OkStatus();
+  }
+
+  // Register non-FIPS key managers.
   auto status = Registry::RegisterKeyTypeManager(
       absl::make_unique<AesSivKeyManager>(), true);
   if (!status.ok()) return status;
diff --git a/cc/daead/deterministic_aead_config_test.cc b/cc/daead/deterministic_aead_config_test.cc
index e05cf16..6683b69 100644
--- a/cc/daead/deterministic_aead_config_test.cc
+++ b/cc/daead/deterministic_aead_config_test.cc
@@ -16,9 +16,12 @@
 
 #include "tink/daead/deterministic_aead_config.h"
 
+#include <list>
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "tink/config.h"
+#include "tink/config/tink_fips.h"
 #include "tink/daead/aes_siv_key_manager.h"
 #include "tink/daead/deterministic_aead_key_templates.h"
 #include "tink/deterministic_aead.h"
@@ -43,6 +46,10 @@
 };
 
 TEST_F(DeterministicAeadConfigTest, Basic) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   EXPECT_THAT(Registry::get_key_manager<DeterministicAead>(
                   AesSivKeyManager().get_key_type())
                   .status(),
@@ -57,6 +64,10 @@
 // Tests that the DeterministicAeadWrapper has been properly registered and we
 // can wrap primitives.
 TEST_F(DeterministicAeadConfigTest, WrappersRegistered) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   ASSERT_TRUE(DeterministicAeadConfig::Register().ok());
 
   google::crypto::tink::Keyset::Key key;
@@ -86,10 +97,28 @@
   EXPECT_THAT(decryption_result.ValueOrDie(), Eq("secret"));
 
   decryption_result = DummyDeterministicAead("dummy").DecryptDeterministically(
-      encryption_result.ValueOrDie(), "wrog");
+      encryption_result.ValueOrDie(), "wrong");
   EXPECT_FALSE(decryption_result.status().ok());
 }
 
+TEST_F(DeterministicAeadConfigTest, RegisterFipsValidTemplates) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  EXPECT_THAT(DeterministicAeadConfig::Register(), IsOk());
+
+  // Check that we can not retrieve non-FIPS key handle
+  std::list<google::crypto::tink::KeyTemplate> non_fips_key_templates;
+  non_fips_key_templates.push_back(DeterministicAeadKeyTemplates::Aes256Siv());
+
+  for (auto key_template : non_fips_key_templates) {
+    auto new_keyset_handle_result = KeysetHandle::GenerateNew(key_template);
+    EXPECT_THAT(new_keyset_handle_result.status(),
+               StatusIs(util::error::NOT_FOUND));
+  }
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/hybrid/BUILD.bazel b/cc/hybrid/BUILD.bazel
index 55736ff..4aeac3c 100644
--- a/cc/hybrid/BUILD.bazel
+++ b/cc/hybrid/BUILD.bazel
@@ -16,6 +16,7 @@
         "//:registry",
         "//aead:aead_config",
         "//config:config_util",
+        "//config:tink_fips",
         "//proto:config_cc_proto",
         "//util:status",
         "@com_google_absl//absl/base:core_headers",
@@ -197,6 +198,7 @@
         "//util:enums",
         "//util:errors",
         "//util:protobuf_helper",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "//util:validation",
@@ -247,6 +249,7 @@
         "//:hybrid_encrypt",
         "//:keyset_handle",
         "//:registry",
+        "//config:tink_fips",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
diff --git a/cc/hybrid/CMakeLists.txt b/cc/hybrid/CMakeLists.txt
index 4c7c1b4..b7a3990 100644
--- a/cc/hybrid/CMakeLists.txt
+++ b/cc/hybrid/CMakeLists.txt
@@ -12,6 +12,7 @@
     tink::hybrid::hybrid_encrypt_wrapper
     tink::core::registry
     tink::config::config_util
+    tink::config::tink_fips
     tink::aead::aead_config
     tink::util::status
     tink::proto::config_cc_proto
@@ -175,6 +176,7 @@
     tink::util::enums
     tink::util::errors
     tink::util::protobuf_helper
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::validation
@@ -214,6 +216,7 @@
   DEPS
     tink::hybrid::hybrid_config
     tink::hybrid::hybrid_key_templates
+    tink::config::tink_fips
     tink::core::config
     tink::core::hybrid_decrypt
     tink::core::hybrid_encrypt
diff --git a/cc/hybrid/ecies_aead_hkdf_dem_helper.cc b/cc/hybrid/ecies_aead_hkdf_dem_helper.cc
index 9377cbf..fea92f1 100644
--- a/cc/hybrid/ecies_aead_hkdf_dem_helper.cc
+++ b/cc/hybrid/ecies_aead_hkdf_dem_helper.cc
@@ -105,34 +105,70 @@
   if (!ReplaceKeyBytes(symmetric_key_value, key.get())) {
     return util::Status(util::error::INTERNAL, "Generation of DEM-key failed.");
   }
-  return key_manager_->GetPrimitive(*key);
+  auto aead_or = key_manager_->GetPrimitive(*key);
+  ZeroKeyBytes(key.get());
+  return aead_or;
 }
 
 bool EciesAeadHkdfDemHelper::ReplaceKeyBytes(
     const util::SecretData& key_bytes,
     portable_proto::MessageLite* proto) const {
-  if (key_params_.key_type == AES_GCM_KEY) {
-    AesGcmKey* key = static_cast<AesGcmKey*>(proto);
-    key->set_key_value(std::string(util::SecretDataAsStringView(key_bytes)));
-    return true;
-  } else if (key_params_.key_type == AES_CTR_HMAC_AEAD_KEY) {
-    AesCtrHmacAeadKey* key = static_cast<AesCtrHmacAeadKey*>(proto);
-    auto aes_ctr_key = key->mutable_aes_ctr_key();
-    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();
-    hmac_key->set_key_value(
-        std::string(util::SecretDataAsStringView(key_bytes).substr(
-            key_params_.aes_ctr_key_size_in_bytes)));
-    return true;
-  } else if (key_params_.key_type == XCHACHA20_POLY1305_KEY) {
-    XChaCha20Poly1305Key* key = static_cast<XChaCha20Poly1305Key*>(proto);
-    key->set_key_value(std::string(util::SecretDataAsStringView(key_bytes)));
-    return true;
+  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_CTR_HMAC_AEAD_KEY: {
+      AesCtrHmacAeadKey* key = static_cast<AesCtrHmacAeadKey*>(proto);
+      auto aes_ctr_key = key->mutable_aes_ctr_key();
+      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();
+      hmac_key->set_key_value(
+          std::string(util::SecretDataAsStringView(key_bytes).substr(
+              key_params_.aes_ctr_key_size_in_bytes)));
+      return true;
+    }
+    case XCHACHA20_POLY1305_KEY: {
+      XChaCha20Poly1305Key* key = static_cast<XChaCha20Poly1305Key*>(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;
+    }
+  }
+}
+
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/hybrid/ecies_aead_hkdf_dem_helper.h b/cc/hybrid/ecies_aead_hkdf_dem_helper.h
index de8d54b..cfb0ecb 100644
--- a/cc/hybrid/ecies_aead_hkdf_dem_helper.h
+++ b/cc/hybrid/ecies_aead_hkdf_dem_helper.h
@@ -51,7 +51,6 @@
 
  private:
   enum DemKeyType {
-    UNKNOWN_KEY = 0,
     AES_GCM_KEY,
     AES_CTR_HMAC_AEAD_KEY,
     XCHACHA20_POLY1305_KEY,
@@ -76,6 +75,8 @@
   bool ReplaceKeyBytes(const util::SecretData& key_bytes,
                        portable_proto::MessageLite* proto) const;
 
+  void ZeroKeyBytes(portable_proto::MessageLite* proto) const;
+
   const KeyManager<Aead>* key_manager_;  // not owned
   const google::crypto::tink::KeyTemplate key_template_;
   const DemKeyParams key_params_;
diff --git a/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc b/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc
index 183cd30..d13883a 100644
--- a/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc
+++ b/cc/hybrid/ecies_aead_hkdf_hybrid_decrypt_test.cc
@@ -204,6 +204,7 @@
 
 TEST_F(EciesAeadHkdfHybridDecryptTest, testGettingHybridEncryptWithoutManager) {
   // Prepare an ECIES key.
+  Registry::Reset();
   auto ecies_key = test::GetEciesAesGcmHkdfTestKey(
       EllipticCurveType::NIST_P256,
       EcPointFormat::UNCOMPRESSED,
diff --git a/cc/hybrid/ecies_aead_hkdf_private_key_manager.cc b/cc/hybrid/ecies_aead_hkdf_private_key_manager.cc
index 03b41a5..2d4ad8b 100644
--- a/cc/hybrid/ecies_aead_hkdf_private_key_manager.cc
+++ b/cc/hybrid/ecies_aead_hkdf_private_key_manager.cc
@@ -16,16 +16,17 @@
 
 #include "tink/hybrid/ecies_aead_hkdf_private_key_manager.h"
 
-#include "absl/strings/string_view.h"
 #include "absl/memory/memory.h"
-#include "tink/hybrid_decrypt.h"
-#include "tink/key_manager.h"
+#include "absl/strings/string_view.h"
 #include "tink/hybrid/ecies_aead_hkdf_hybrid_decrypt.h"
 #include "tink/hybrid/ecies_aead_hkdf_public_key_manager.h"
+#include "tink/hybrid_decrypt.h"
+#include "tink/key_manager.h"
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/enums.h"
 #include "tink/util/errors.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/validation.h"
@@ -63,7 +64,8 @@
   // Build EciesAeadHkdfPrivateKey.
   EciesAeadHkdfPrivateKey ecies_private_key;
   ecies_private_key.set_version(get_version());
-  ecies_private_key.set_key_value(ec_key.priv);
+  ecies_private_key.set_key_value(
+      std::string(util::SecretDataAsStringView(ec_key.priv)));
   auto ecies_public_key = ecies_private_key.mutable_public_key();
   ecies_public_key->set_version(get_version());
   ecies_public_key->set_x(ec_key.pub_x);
diff --git a/cc/hybrid/hybrid_config.cc b/cc/hybrid/hybrid_config.cc
index 4f44d8b..3176fd1 100644
--- a/cc/hybrid/hybrid_config.cc
+++ b/cc/hybrid/hybrid_config.cc
@@ -21,6 +21,7 @@
 #include "tink/hybrid/ecies_aead_hkdf_private_key_manager.h"
 #include "tink/hybrid/ecies_aead_hkdf_public_key_manager.h"
 #include "tink/config/config_util.h"
+#include "tink/config/tink_fips.h"
 #include "tink/registry.h"
 #include "tink/hybrid/hybrid_decrypt_wrapper.h"
 #include "tink/hybrid/hybrid_encrypt_wrapper.h"
@@ -42,19 +43,29 @@
 util::Status HybridConfig::Register() {
   auto status = AeadConfig::Register();
 
-  // Register key managers.
+  // Register primitive wrappers.
+  status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<HybridEncryptWrapper>());
+  if (!status.ok()) return status;
+  status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<HybridDecryptWrapper>());
+  if (!status.ok()) return status;
+
+  // Currently there are no hybrid encryption key managers which only use
+  // FIPS-validated implementations, therefore none will be registered in
+  // FIPS only mode.
+  if (kUseOnlyFips) {
+    return util::OkStatus();
+  }
+
+  // Register non-FIPS key managers.
   if (!status.ok()) return status;
   status = Registry::RegisterAsymmetricKeyManagers(
       absl::make_unique<EciesAeadHkdfPrivateKeyManager>(),
       absl::make_unique<EciesAeadHkdfPublicKeyManager>(), true);
   if (!status.ok()) return status;
 
-  // Register primitive wrappers.
-  status = Registry::RegisterPrimitiveWrapper(
-      absl::make_unique<HybridEncryptWrapper>());
-  if (!status.ok()) return status;
-  return Registry::RegisterPrimitiveWrapper(
-      absl::make_unique<HybridDecryptWrapper>());
+  return util::OkStatus();
 }
 
 }  // namespace tink
diff --git a/cc/hybrid/hybrid_config_test.cc b/cc/hybrid/hybrid_config_test.cc
index 2479bad..fe72668 100644
--- a/cc/hybrid/hybrid_config_test.cc
+++ b/cc/hybrid/hybrid_config_test.cc
@@ -16,9 +16,12 @@
 
 #include "tink/hybrid/hybrid_config.h"
 
+#include <list>
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "tink/config.h"
+#include "tink/config/tink_fips.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"
@@ -45,6 +48,10 @@
 };
 
 TEST_F(HybridConfigTest, Basic) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   EXPECT_THAT(Registry::get_key_manager<HybridDecrypt>(
                   EciesAeadHkdfPrivateKeyManager().get_key_type())
                   .status(),
@@ -67,6 +74,10 @@
 // Tests that the HybridEncryptWrapper has been properly registered and we
 // can wrap primitives.
 TEST_F(HybridConfigTest, EncryptWrapperRegistered) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   ASSERT_TRUE(HybridConfig::Register().ok());
 
   google::crypto::tink::Keyset::Key key;
@@ -98,6 +109,10 @@
 // Tests that the HybridDecryptWrapper has been properly registered and we
 // can wrap primitives.
 TEST_F(HybridConfigTest, DecryptWrapperRegistered) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   ASSERT_TRUE(HybridConfig::Register().ok());
 
   google::crypto::tink::Keyset::Key key;
@@ -126,6 +141,41 @@
             "secret");
 }
 
+// FIPS-only mode tests
+TEST_F(HybridConfigTest, RegisterNonFipsTemplates) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  EXPECT_THAT(HybridConfig::Register(), IsOk());
+
+  // Check that we can not retrieve non-FIPS keyset handle
+  std::list<google::crypto::tink::KeyTemplate> non_fips_key_templates;
+  non_fips_key_templates.push_back(
+      HybridKeyTemplates::
+          EciesP256CompressedHkdfHmacSha256Aes128CtrHmacSha256());
+  non_fips_key_templates.push_back(
+      HybridKeyTemplates::EciesP256CompressedHkdfHmacSha256Aes128Gcm());
+  non_fips_key_templates.push_back(
+      HybridKeyTemplates::EciesP256HkdfHmacSha256Aes128CtrHmacSha256());
+  non_fips_key_templates.push_back(
+      HybridKeyTemplates::EciesP256HkdfHmacSha256Aes128Gcm());
+  non_fips_key_templates.push_back(
+      HybridKeyTemplates::
+          EciesP256HkdfHmacSha256Aes128GcmCompressedWithoutPrefix());
+  non_fips_key_templates.push_back(
+      HybridKeyTemplates::EciesX25519HkdfHmacSha256Aes128CtrHmacSha256());
+  non_fips_key_templates.push_back(
+      HybridKeyTemplates::EciesX25519HkdfHmacSha256Aes128Gcm());
+  non_fips_key_templates.push_back(
+      HybridKeyTemplates::EciesX25519HkdfHmacSha256XChaCha20Poly1305());
+
+  for (auto key_template : non_fips_key_templates) {
+    EXPECT_THAT(KeysetHandle::GenerateNew(key_template).status(),
+                StatusIs(util::error::NOT_FOUND));
+  }
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/integration/gcpkms/gcp_kms_aead.cc b/cc/integration/gcpkms/gcp_kms_aead.cc
index be752c6..620cac6 100644
--- a/cc/integration/gcpkms/gcp_kms_aead.cc
+++ b/cc/integration/gcpkms/gcp_kms_aead.cc
@@ -67,6 +67,9 @@
 
   EncryptResponse resp;
   ClientContext context;
+  context.AddMetadata("x-goog-request-params",
+                      absl::StrCat("name=", key_name_));
+
   auto status =  kms_stub_->Encrypt(&context, req, &resp);
 
   if (status.ok()) return resp.ciphertext();
@@ -83,6 +86,9 @@
 
   DecryptResponse resp;
   ClientContext context;
+  context.AddMetadata("x-goog-request-params",
+                      absl::StrCat("name=", key_name_));
+
   auto status =  kms_stub_->Decrypt(&context, req, &resp);
 
   if (status.ok()) return resp.plaintext();
diff --git a/cc/integration/gcpkms/gcp_kms_aead_test.cc b/cc/integration/gcpkms/gcp_kms_aead_test.cc
index 846064d..0d1251a 100644
--- a/cc/integration/gcpkms/gcp_kms_aead_test.cc
+++ b/cc/integration/gcpkms/gcp_kms_aead_test.cc
@@ -23,7 +23,8 @@
 using crypto::tink::integration::gcpkms::GcpKmsAead;
 
 class GcpKmsAeadTest : public ::testing::Test {
-  // TODO(przydatek): add a test with a mock KMSClient.
+  // TODO(kste): Add tests when mock for
+  // google::cloud::kms::v1::KeyManagementService::StubInterface is available.
 };
 
 
diff --git a/cc/mac/BUILD.bazel b/cc/mac/BUILD.bazel
index 7491832..960a7f3 100644
--- a/cc/mac/BUILD.bazel
+++ b/cc/mac/BUILD.bazel
@@ -31,6 +31,7 @@
         ":mac_wrapper",
         "//:registry",
         "//config:config_util",
+        "//config:tink_fips",
         "//proto:config_cc_proto",
         "//util:status",
         "@com_google_absl//absl/base:core_headers",
@@ -156,6 +157,7 @@
         "//:keyset_handle",
         "//:mac",
         "//:registry",
+        "//config:tink_fips",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
diff --git a/cc/mac/CMakeLists.txt b/cc/mac/CMakeLists.txt
index 89fdfbb..b79e398 100644
--- a/cc/mac/CMakeLists.txt
+++ b/cc/mac/CMakeLists.txt
@@ -26,6 +26,7 @@
     tink::mac::hmac_key_manager
     tink::mac::mac_wrapper
     tink::config::config_util
+    tink::config::tink_fips
     tink::core::registry
     tink::util::status
     tink::proto::config_cc_proto
@@ -136,6 +137,7 @@
     tink::mac::hmac_key_manager
     tink::mac::mac_config
     tink::mac::mac_key_templates
+    tink::config::tink_fips
     tink::core::config
     tink::core::keyset_handle
     tink::core::mac
diff --git a/cc/mac/mac_config.cc b/cc/mac/mac_config.cc
index b891285..99d9110 100644
--- a/cc/mac/mac_config.cc
+++ b/cc/mac/mac_config.cc
@@ -18,6 +18,7 @@
 
 #include "absl/memory/memory.h"
 #include "tink/config/config_util.h"
+#include "tink/config/tink_fips.h"
 #include "tink/mac/aes_cmac_key_manager.h"
 #include "tink/mac/hmac_key_manager.h"
 #include "tink/mac/mac_wrapper.h"
@@ -38,16 +39,27 @@
 
 // static
 util::Status MacConfig::Register() {
-  // Register key managers.
-  auto status = Registry::RegisterKeyTypeManager(
-      absl::make_unique<HmacKeyManager>(), true);
+  // Register primitive wrapper.
+  auto status =
+      Registry::RegisterPrimitiveWrapper(absl::make_unique<MacWrapper>());
   if (!status.ok()) return status;
+
+  // Register key managers which utilize the FIPS validated BoringCrypto
+  // implementations.
+  status = Registry::RegisterKeyTypeManager(absl::make_unique<HmacKeyManager>(),
+                                            true);
+  if (!status.ok()) return status;
+
+  if (kUseOnlyFips) {
+    return util::OkStatus();
+  }
+
+  // CMac in BoringSSL is not FIPS validated.
   status = Registry::RegisterKeyTypeManager(
       absl::make_unique<AesCmacKeyManager>(), true);
   if (!status.ok()) return status;
 
-  // Register primitive wrapper.
-  return Registry::RegisterPrimitiveWrapper(absl::make_unique<MacWrapper>());
+  return util::OkStatus();
 }
 
 }  // namespace tink
diff --git a/cc/mac/mac_config_test.cc b/cc/mac/mac_config_test.cc
index a6208e6..f4048ce 100644
--- a/cc/mac/mac_config_test.cc
+++ b/cc/mac/mac_config_test.cc
@@ -16,8 +16,11 @@
 
 #include "tink/mac/mac_config.h"
 
+#include <list>
+
 #include "gtest/gtest.h"
 #include "tink/config.h"
+#include "tink/config/tink_fips.h"
 #include "tink/keyset_handle.h"
 #include "tink/mac.h"
 #include "tink/mac/hmac_key_manager.h"
@@ -84,6 +87,41 @@
       DummyMac("dummy").VerifyMac(mac_result.ValueOrDie(), "faked text").ok());
 }
 
+// FIPS-only mode tests
+TEST_F(MacConfigTest, RegisterNonFipsTemplates) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  EXPECT_THAT(MacConfig::Register(), IsOk());
+
+  std::list<google::crypto::tink::KeyTemplate> non_fips_key_templates;
+  non_fips_key_templates.push_back(MacKeyTemplates::AesCmac());
+
+  for (auto key_template : non_fips_key_templates) {
+    EXPECT_THAT(KeysetHandle::GenerateNew(key_template).status(),
+                StatusIs(util::error::NOT_FOUND));
+  }
+}
+
+TEST_F(MacConfigTest, RegisterFipsValidTemplates) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  EXPECT_THAT(MacConfig::Register(), IsOk());
+
+  std::list<google::crypto::tink::KeyTemplate> fips_key_templates;
+  fips_key_templates.push_back(MacKeyTemplates::HmacSha256());
+  fips_key_templates.push_back(MacKeyTemplates::HmacSha256HalfSizeTag());
+  fips_key_templates.push_back(MacKeyTemplates::HmacSha512());
+  fips_key_templates.push_back(MacKeyTemplates::HmacSha512HalfSizeTag());
+
+  for (auto key_template : fips_key_templates) {
+    EXPECT_THAT(KeysetHandle::GenerateNew(key_template).status(), IsOk());
+  }
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/signature/BUILD.bazel b/cc/signature/BUILD.bazel
index 4ecf857..5748a6b 100644
--- a/cc/signature/BUILD.bazel
+++ b/cc/signature/BUILD.bazel
@@ -111,6 +111,7 @@
         "//util:enums",
         "//util:errors",
         "//util:protobuf_helper",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "//util:validation",
@@ -219,6 +220,7 @@
         "//util:enums",
         "//util:errors",
         "//util:protobuf_helper",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "@com_google_absl//absl/memory",
@@ -270,6 +272,7 @@
         "//util:enums",
         "//util:errors",
         "//util:protobuf_helper",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "@com_google_absl//absl/memory",
@@ -296,6 +299,7 @@
         "//util:enums",
         "//util:errors",
         "//util:protobuf_helper",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "@com_google_absl//absl/memory",
@@ -322,6 +326,7 @@
         ":rsa_ssa_pss_verify_key_manager",
         "//:registry",
         "//config:config_util",
+        "//config:tink_fips",
         "//proto:config_cc_proto",
         "//util:status",
         "@com_google_absl//absl/base:core_headers",
@@ -348,6 +353,7 @@
         "//subtle:subtle_util_boringssl",
         "//util:enums",
         "//util:keyset_util",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "@com_google_absl//absl/memory",
@@ -456,6 +462,7 @@
         "//subtle:ecdsa_sign_boringssl",
         "//subtle:subtle_util_boringssl",
         "//util:enums",
+        "//util:secret_data",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
@@ -500,6 +507,7 @@
         "//proto:tink_cc_proto",
         "//subtle:rsa_ssa_pkcs1_sign_boringssl",
         "//subtle:subtle_util_boringssl",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
@@ -523,6 +531,7 @@
         "//proto:rsa_ssa_pss_cc_proto",
         "//subtle:rsa_ssa_pss_sign_boringssl",
         "//subtle:subtle_util_boringssl",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
@@ -590,6 +599,7 @@
         "//proto:tink_cc_proto",
         "//subtle:rsa_ssa_pkcs1_verify_boringssl",
         "//subtle:subtle_util_boringssl",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
@@ -614,6 +624,7 @@
         "//proto:rsa_ssa_pss_cc_proto",
         "//subtle:rsa_ssa_pss_verify_boringssl",
         "//subtle:subtle_util_boringssl",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
@@ -639,6 +650,7 @@
         "//:public_key_sign",
         "//:public_key_verify",
         "//:registry",
+        "//config:tink_fips",
         "//util:status",
         "//util:test_matchers",
         "//util:test_util",
@@ -689,6 +701,7 @@
         "//subtle:subtle_util_boringssl",
         "//util:enums",
         "//util:keyset_util",
+        "//util:secret_data",
         "//util:status",
         "//util:test_matchers",
         "@com_google_absl//absl/memory",
diff --git a/cc/signature/CMakeLists.txt b/cc/signature/CMakeLists.txt
index 3aa62f2..85611e9 100644
--- a/cc/signature/CMakeLists.txt
+++ b/cc/signature/CMakeLists.txt
@@ -102,6 +102,7 @@
     tink::util::enums
     tink::util::errors
     tink::util::protobuf_helper
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::proto::ecdsa_cc_proto
@@ -203,6 +204,7 @@
     tink::util::enums
     tink::util::errors
     tink::util::protobuf_helper
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::validation
@@ -250,6 +252,7 @@
     tink::util::enums
     tink::util::errors
     tink::util::protobuf_helper
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::proto::common_cc_proto
@@ -274,6 +277,7 @@
     tink::util::enums
     tink::util::errors
     tink::util::protobuf_helper
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::proto::common_cc_proto
@@ -300,6 +304,7 @@
     tink::signature::public_key_sign_wrapper
     tink::signature::public_key_verify_wrapper
     tink::config::config_util
+    tink::config::tink_fips
     tink::core::registry
     tink::util::status
     tink::proto::config_cc_proto
@@ -321,6 +326,7 @@
     tink::subtle::subtle_util_boringssl
     tink::util::enums
     tink::util::keyset_util
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::proto::common_cc_proto
@@ -409,6 +415,7 @@
     tink::subtle::ecdsa_sign_boringssl
     tink::subtle::subtle_util_boringssl
     tink::util::enums
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::test_matchers
@@ -445,6 +452,7 @@
     tink::signature::rsa_ssa_pkcs1_verify_key_manager
     tink::core::public_key_sign
     tink::core::public_key_verify
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::test_util
@@ -464,6 +472,7 @@
     tink::signature::rsa_ssa_pss_verify_key_manager
     tink::core::public_key_sign
     tink::core::public_key_verify
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::test_util
@@ -524,6 +533,7 @@
     tink::core::public_key_verify
     tink::subtle::rsa_ssa_pkcs1_verify_boringssl
     tink::subtle::subtle_util_boringssl
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::test_matchers
@@ -545,6 +555,7 @@
     tink::core::public_key_sign
     tink::subtle::rsa_ssa_pss_verify_boringssl
     tink::subtle::subtle_util_boringssl
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::test_matchers
@@ -563,6 +574,7 @@
     tink::signature::rsa_ssa_pss_verify_key_manager
     tink::signature::signature_config
     tink::signature::signature_key_templates
+    tink::config::tink_fips
     tink::core::config
     tink::core::keyset_handle
     tink::core::public_key_sign
@@ -606,6 +618,7 @@
     tink::subtle::subtle_util_boringssl
     tink::util::enums
     tink::util::keyset_util
+    tink::util::secret_data
     tink::util::status
     tink::util::test_util
     tink::proto::common_cc_proto
diff --git a/cc/signature/ecdsa_sign_key_manager.cc b/cc/signature/ecdsa_sign_key_manager.cc
index 3449e4e..2adaaf2 100644
--- a/cc/signature/ecdsa_sign_key_manager.cc
+++ b/cc/signature/ecdsa_sign_key_manager.cc
@@ -25,6 +25,7 @@
 #include "tink/util/enums.h"
 #include "tink/util/errors.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/validation.h"
@@ -51,7 +52,8 @@
   // Build EcdsaPrivateKey.
   EcdsaPrivateKey ecdsa_private_key;
   ecdsa_private_key.set_version(get_version());
-  ecdsa_private_key.set_key_value(ec_key.priv);
+  ecdsa_private_key.set_key_value(
+      std::string(util::SecretDataAsStringView(ec_key.priv)));
   auto ecdsa_public_key = ecdsa_private_key.mutable_public_key();
   ecdsa_public_key->set_version(get_version());
   ecdsa_public_key->set_x(ec_key.pub_x);
@@ -68,7 +70,7 @@
   ec_key.curve = Enums::ProtoToSubtle(public_key.params().curve());
   ec_key.pub_x = public_key.x();
   ec_key.pub_y = public_key.y();
-  ec_key.priv = ecdsa_private_key.key_value();
+  ec_key.priv = util::SecretDataFromStringView(ecdsa_private_key.key_value());
   auto result = subtle::EcdsaSignBoringSsl::New(
       ec_key, Enums::ProtoToSubtle(public_key.params().hash_type()),
       Enums::ProtoToSubtle(public_key.params().encoding()));
diff --git a/cc/signature/ecdsa_verify_key_manager_test.cc b/cc/signature/ecdsa_verify_key_manager_test.cc
index 5fa27e6..030816e 100644
--- a/cc/signature/ecdsa_verify_key_manager_test.cc
+++ b/cc/signature/ecdsa_verify_key_manager_test.cc
@@ -24,6 +24,7 @@
 #include "tink/subtle/ecdsa_sign_boringssl.h"
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/enums.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
@@ -167,7 +168,7 @@
   ec_key.curve = Enums::ProtoToSubtle(public_key.params().curve());
   ec_key.pub_x = public_key.x();
   ec_key.pub_y = public_key.y();
-  ec_key.priv = private_key.key_value();
+  ec_key.priv = util::SecretDataFromStringView(private_key.key_value());
 
   auto direct_signer_or = subtle::EcdsaSignBoringSsl::New(
       ec_key, Enums::ProtoToSubtle(public_key.params().hash_type()),
@@ -195,7 +196,7 @@
   ec_key.curve = Enums::ProtoToSubtle(public_key.params().curve());
   ec_key.pub_x = public_key.x();
   ec_key.pub_y = public_key.y();
-  ec_key.priv = private_key.key_value();
+  ec_key.priv = util::SecretDataFromStringView(private_key.key_value());
 
   auto direct_signer_or = subtle::EcdsaSignBoringSsl::New(
       ec_key, Enums::ProtoToSubtle(public_key.params().hash_type()),
diff --git a/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc
index b7a791b..9ecae29 100644
--- a/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc
+++ b/cc/signature/rsa_ssa_pkcs1_sign_key_manager.cc
@@ -27,6 +27,7 @@
 #include "tink/util/enums.h"
 #include "tink/util/errors.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/validation.h"
@@ -47,12 +48,12 @@
     const subtle::SubtleUtilBoringSSL::RsaPrivateKey& private_key) {
   RsaSsaPkcs1PrivateKey key_proto;
   key_proto.set_version(RsaSsaPkcs1SignKeyManager().get_version());
-  key_proto.set_d(private_key.d);
-  key_proto.set_p(private_key.p);
-  key_proto.set_q(private_key.q);
-  key_proto.set_dp(private_key.dp);
-  key_proto.set_dq(private_key.dq);
-  key_proto.set_crt(private_key.crt);
+  key_proto.set_d(std::string(util::SecretDataAsStringView(private_key.d)));
+  key_proto.set_p(std::string(util::SecretDataAsStringView(private_key.p)));
+  key_proto.set_q(std::string(util::SecretDataAsStringView(private_key.q)));
+  key_proto.set_dp(std::string(util::SecretDataAsStringView(private_key.dp)));
+  key_proto.set_dq(std::string(util::SecretDataAsStringView(private_key.dq)));
+  key_proto.set_crt(std::string(util::SecretDataAsStringView(private_key.crt)));
   auto* public_key_proto = key_proto.mutable_public_key();
   public_key_proto->set_version(RsaSsaPkcs1SignKeyManager().get_version());
   public_key_proto->set_n(private_key.n);
@@ -65,12 +66,12 @@
   subtle::SubtleUtilBoringSSL::RsaPrivateKey key;
   key.n = key_proto.public_key().n();
   key.e = key_proto.public_key().e();
-  key.d = key_proto.d();
-  key.p = key_proto.p();
-  key.q = key_proto.q();
-  key.dp = key_proto.dp();
-  key.dq = key_proto.dq();
-  key.crt = key_proto.crt();
+  key.d = util::SecretDataFromStringView(key_proto.d());
+  key.p = util::SecretDataFromStringView(key_proto.p());
+  key.q = util::SecretDataFromStringView(key_proto.q());
+  key.dp = util::SecretDataFromStringView(key_proto.dp());
+  key.dq = util::SecretDataFromStringView(key_proto.dq());
+  key.crt = util::SecretDataFromStringView(key_proto.crt());
   return key;
 }
 
diff --git a/cc/signature/rsa_ssa_pkcs1_verify_key_manager_test.cc b/cc/signature/rsa_ssa_pkcs1_verify_key_manager_test.cc
index 93e54fd..d6d5739 100644
--- a/cc/signature/rsa_ssa_pkcs1_verify_key_manager_test.cc
+++ b/cc/signature/rsa_ssa_pkcs1_verify_key_manager_test.cc
@@ -26,6 +26,7 @@
 #include "tink/signature/rsa_ssa_pkcs1_sign_key_manager.h"
 #include "tink/subtle/rsa_ssa_pkcs1_sign_boringssl.h"
 #include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
@@ -133,12 +134,12 @@
   subtle::SubtleUtilBoringSSL::RsaPrivateKey private_key_subtle;
   private_key_subtle.n = private_key.public_key().n();
   private_key_subtle.e = private_key.public_key().e();
-  private_key_subtle.d = private_key.d();
-  private_key_subtle.p = private_key.p();
-  private_key_subtle.q = private_key.q();
-  private_key_subtle.dp = private_key.dp();
-  private_key_subtle.dq = private_key.dq();
-  private_key_subtle.crt = private_key.crt();
+  private_key_subtle.d = util::SecretDataFromStringView(private_key.d());
+  private_key_subtle.p = util::SecretDataFromStringView(private_key.p());
+  private_key_subtle.q = util::SecretDataFromStringView(private_key.q());
+  private_key_subtle.dp = util::SecretDataFromStringView(private_key.dp());
+  private_key_subtle.dq = util::SecretDataFromStringView(private_key.dq());
+  private_key_subtle.crt = util::SecretDataFromStringView(private_key.crt());
 
   auto direct_signer_or = subtle::RsaSsaPkcs1SignBoringSsl::New(
       private_key_subtle, {crypto::tink::subtle::HashType::SHA256});
diff --git a/cc/signature/rsa_ssa_pss_sign_key_manager.cc b/cc/signature/rsa_ssa_pss_sign_key_manager.cc
index 5f0ccba..e8b947b 100644
--- a/cc/signature/rsa_ssa_pss_sign_key_manager.cc
+++ b/cc/signature/rsa_ssa_pss_sign_key_manager.cc
@@ -26,6 +26,7 @@
 #include "tink/util/enums.h"
 #include "tink/util/errors.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/validation.h"
@@ -47,12 +48,13 @@
     const subtle::SubtleUtilBoringSSL::RsaPrivateKey& private_key) {
   auto key_proto = absl::make_unique<RsaSsaPssPrivateKey>();
   key_proto->set_version(RsaSsaPssSignKeyManager().get_version());
-  key_proto->set_d(private_key.d);
-  key_proto->set_p(private_key.p);
-  key_proto->set_q(private_key.q);
-  key_proto->set_dp(private_key.dp);
-  key_proto->set_dq(private_key.dq);
-  key_proto->set_crt(private_key.crt);
+  key_proto->set_d(std::string(util::SecretDataAsStringView(private_key.d)));
+  key_proto->set_p(std::string(util::SecretDataAsStringView(private_key.p)));
+  key_proto->set_q(std::string(util::SecretDataAsStringView(private_key.q)));
+  key_proto->set_dp(std::string(util::SecretDataAsStringView(private_key.dp)));
+  key_proto->set_dq(std::string(util::SecretDataAsStringView(private_key.dq)));
+  key_proto->set_crt(
+      std::string(util::SecretDataAsStringView(private_key.crt)));
   auto* public_key_proto = key_proto->mutable_public_key();
   public_key_proto->set_version(RsaSsaPssSignKeyManager().get_version());
   public_key_proto->set_n(private_key.n);
@@ -65,12 +67,12 @@
   subtle::SubtleUtilBoringSSL::RsaPrivateKey key;
   key.n = key_proto.public_key().n();
   key.e = key_proto.public_key().e();
-  key.d = key_proto.d();
-  key.p = key_proto.p();
-  key.q = key_proto.q();
-  key.dp = key_proto.dp();
-  key.dq = key_proto.dq();
-  key.crt = key_proto.crt();
+  key.d = util::SecretDataFromStringView(key_proto.d());
+  key.p = util::SecretDataFromStringView(key_proto.p());
+  key.q = util::SecretDataFromStringView(key_proto.q());
+  key.dp = util::SecretDataFromStringView(key_proto.dp());
+  key.dq = util::SecretDataFromStringView(key_proto.dq());
+  key.crt = util::SecretDataFromStringView(key_proto.crt());
   return key;
 }
 
diff --git a/cc/signature/rsa_ssa_pss_verify_key_manager.cc b/cc/signature/rsa_ssa_pss_verify_key_manager.cc
index 92245e8..e3461e4 100644
--- a/cc/signature/rsa_ssa_pss_verify_key_manager.cc
+++ b/cc/signature/rsa_ssa_pss_verify_key_manager.cc
@@ -30,7 +30,7 @@
 #include "proto/tink.pb.h"
 
 // TODO(quannguyen):
-//  + Validate salt length and possible e.
+//  + Validate possible e.
 namespace crypto {
 namespace tink {
 
@@ -92,6 +92,10 @@
                      "MGF1 hash '%d' is different from signature hash '%d'",
                      params.mgf1_hash(), params.sig_hash());
   }
+  if (params.salt_length() < 0) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "salt length is negative");
+  }
   return Status::OK;
 }
 
diff --git a/cc/signature/rsa_ssa_pss_verify_key_manager_test.cc b/cc/signature/rsa_ssa_pss_verify_key_manager_test.cc
index a8fa3a9..fb5c572 100644
--- a/cc/signature/rsa_ssa_pss_verify_key_manager_test.cc
+++ b/cc/signature/rsa_ssa_pss_verify_key_manager_test.cc
@@ -25,6 +25,7 @@
 #include "tink/signature/rsa_ssa_pss_sign_key_manager.h"
 #include "tink/subtle/rsa_ssa_pss_sign_boringssl.h"
 #include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
@@ -135,6 +136,12 @@
                        HasSubstr("only modulus size >= 2048")));
 }
 
+TEST(RsaSsaPssVerifyKeyManagerTest, NegativeSaltLengthFails) {
+  RsaSsaPssPublicKey key = CreateValidPublicKey();
+  key.mutable_params()->set_salt_length(-5);
+  EXPECT_THAT(RsaSsaPssVerifyKeyManager().ValidateKey(key), Not(IsOk()));
+}
+
 TEST(RsaSsaPssSignKeyManagerTest, Create) {
   RsaSsaPssKeyFormat key_format =
       CreateKeyFormat(HashType::SHA256, HashType::SHA256, 32, 3072, RSA_F4);
@@ -148,12 +155,12 @@
   subtle::SubtleUtilBoringSSL::RsaPrivateKey private_key_subtle;
   private_key_subtle.n = private_key.public_key().n();
   private_key_subtle.e = private_key.public_key().e();
-  private_key_subtle.d = private_key.d();
-  private_key_subtle.p = private_key.p();
-  private_key_subtle.q = private_key.q();
-  private_key_subtle.dp = private_key.dp();
-  private_key_subtle.dq = private_key.dq();
-  private_key_subtle.crt = private_key.crt();
+  private_key_subtle.d = util::SecretDataFromStringView(private_key.d());
+  private_key_subtle.p = util::SecretDataFromStringView(private_key.p());
+  private_key_subtle.q = util::SecretDataFromStringView(private_key.q());
+  private_key_subtle.dp = util::SecretDataFromStringView(private_key.dp());
+  private_key_subtle.dq = util::SecretDataFromStringView(private_key.dq());
+  private_key_subtle.crt = util::SecretDataFromStringView(private_key.crt());
 
   auto direct_signer_or = subtle::RsaSsaPssSignBoringSsl::New(
       private_key_subtle, {crypto::tink::subtle::HashType::SHA256,
diff --git a/cc/signature/signature_config.cc b/cc/signature/signature_config.cc
index 75f463d..b5e6e75 100644
--- a/cc/signature/signature_config.cc
+++ b/cc/signature/signature_config.cc
@@ -18,6 +18,7 @@
 
 #include "absl/memory/memory.h"
 #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/ed25519_sign_key_manager.h"
@@ -45,17 +46,20 @@
 
 // static
 util::Status SignatureConfig::Register() {
-  // Register key managers.
-  // ECDSA
-  auto status = Registry::RegisterAsymmetricKeyManagers(
-      absl::make_unique<EcdsaSignKeyManager>(),
-      absl::make_unique<EcdsaVerifyKeyManager>(), true);
+  // Register primitive wrappers.
+  auto status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<PublicKeySignWrapper>());
+  if (!status.ok()) return status;
+  status = Registry::RegisterPrimitiveWrapper(
+      absl::make_unique<PublicKeyVerifyWrapper>());
   if (!status.ok()) return status;
 
-  // ED25519
+  // Register key managers which utilize FIPS validated BoringCrypto
+  // implementations.
+  // ECDSA
   status = Registry::RegisterAsymmetricKeyManagers(
-      absl::make_unique<Ed25519SignKeyManager>(),
-      absl::make_unique<Ed25519VerifyKeyManager>(), true);
+      absl::make_unique<EcdsaSignKeyManager>(),
+      absl::make_unique<EcdsaVerifyKeyManager>(), true);
   if (!status.ok()) return status;
 
   // RSA SSA PSS
@@ -70,12 +74,17 @@
       absl::make_unique<RsaSsaPkcs1VerifyKeyManager>(), true);
   if (!status.ok()) return status;
 
-  // Register primitive wrappers.
-  status = Registry::RegisterPrimitiveWrapper(
-      absl::make_unique<PublicKeySignWrapper>());
+  if (kUseOnlyFips) {
+    return util::OkStatus();
+  }
+
+  // ED25519
+  status = Registry::RegisterAsymmetricKeyManagers(
+      absl::make_unique<Ed25519SignKeyManager>(),
+      absl::make_unique<Ed25519VerifyKeyManager>(), true);
   if (!status.ok()) return status;
-  return  Registry::RegisterPrimitiveWrapper(
-      absl::make_unique<PublicKeyVerifyWrapper>());
+
+  return util::OkStatus();
 }
 
 }  // namespace tink
diff --git a/cc/signature/signature_config_test.cc b/cc/signature/signature_config_test.cc
index 46f2f90..f1bf2ab 100644
--- a/cc/signature/signature_config_test.cc
+++ b/cc/signature/signature_config_test.cc
@@ -16,10 +16,13 @@
 
 #include "tink/signature/signature_config.h"
 
+#include <list>
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include "absl/memory/memory.h"
 #include "tink/config.h"
+#include "tink/config/tink_fips.h"
 #include "tink/keyset_handle.h"
 #include "tink/public_key_sign.h"
 #include "tink/public_key_verify.h"
@@ -39,6 +42,7 @@
 using ::crypto::tink::test::DummyPublicKeyVerify;
 using ::crypto::tink::test::IsOk;
 using ::crypto::tink::test::StatusIs;
+using ::testing::Not;
 
 class SignatureConfigTest : public ::testing::Test {
  protected:
@@ -122,6 +126,56 @@
                   .ok());
 }
 
+// FIPS-only mode tests
+TEST_F(SignatureConfigTest, RegisterNonFipsTemplates) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  EXPECT_THAT(SignatureConfig::Register(), IsOk());
+
+  std::list<google::crypto::tink::KeyTemplate> non_fips_key_templates;
+  non_fips_key_templates.push_back(SignatureKeyTemplates::Ed25519());
+  non_fips_key_templates.push_back(
+      SignatureKeyTemplates::Ed25519WithRawOutput());
+  // 4096-bit RSA is not validated.
+  non_fips_key_templates.push_back(
+      SignatureKeyTemplates::RsaSsaPkcs14096Sha512F4());
+  non_fips_key_templates.push_back(
+      SignatureKeyTemplates::RsaSsaPss4096Sha384Sha384F4());
+  non_fips_key_templates.push_back(
+      SignatureKeyTemplates::RsaSsaPss4096Sha512Sha512F4());
+
+  for (auto key_template : non_fips_key_templates) {
+    EXPECT_THAT(KeysetHandle::GenerateNew(key_template).status(),
+                Not(IsOk()));
+  }
+}
+
+TEST_F(SignatureConfigTest, RegisterFipsValidTemplates) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  EXPECT_THAT(SignatureConfig::Register(), IsOk());
+
+  std::list<google::crypto::tink::KeyTemplate> fips_key_templates;
+  fips_key_templates.push_back(SignatureKeyTemplates::EcdsaP256());
+  fips_key_templates.push_back(SignatureKeyTemplates::EcdsaP256Ieee());
+  fips_key_templates.push_back(SignatureKeyTemplates::EcdsaP384());
+  fips_key_templates.push_back(SignatureKeyTemplates::EcdsaP384Ieee());
+  fips_key_templates.push_back(SignatureKeyTemplates::EcdsaP521());
+  fips_key_templates.push_back(SignatureKeyTemplates::EcdsaP521Ieee());
+  fips_key_templates.push_back(
+      SignatureKeyTemplates::RsaSsaPkcs13072Sha256F4());
+  fips_key_templates.push_back(
+      SignatureKeyTemplates::RsaSsaPss3072Sha256Sha256F4());
+
+  for (auto key_template : fips_key_templates) {
+    EXPECT_THAT(KeysetHandle::GenerateNew(key_template).status(), IsOk());
+  }
+}
+
 }  // namespace
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/signature/signature_pem_keyset_reader.cc b/cc/signature/signature_pem_keyset_reader.cc
index df4f305..da05524 100644
--- a/cc/signature/signature_pem_keyset_reader.cc
+++ b/cc/signature/signature_pem_keyset_reader.cc
@@ -32,6 +32,7 @@
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/enums.h"
 #include "tink/util/keyset_util.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/common.pb.h"
@@ -101,12 +102,18 @@
 
   // RSA Private key parameters.
   private_key_proto.set_version(key_version);
-  private_key_proto.set_d(private_key_subtle.d);
-  private_key_proto.set_p(private_key_subtle.p);
-  private_key_proto.set_q(private_key_subtle.q);
-  private_key_proto.set_dp(private_key_subtle.dp);
-  private_key_proto.set_dq(private_key_subtle.dq);
-  private_key_proto.set_crt(private_key_subtle.crt);
+  private_key_proto.set_d(
+      std::string(util::SecretDataAsStringView(private_key_subtle.d)));
+  private_key_proto.set_p(
+      std::string(util::SecretDataAsStringView(private_key_subtle.p)));
+  private_key_proto.set_q(
+      std::string(util::SecretDataAsStringView(private_key_subtle.q)));
+  private_key_proto.set_dp(
+      std::string(util::SecretDataAsStringView(private_key_subtle.dp)));
+  private_key_proto.set_dq(
+      std::string(util::SecretDataAsStringView(private_key_subtle.dq)));
+  private_key_proto.set_crt(
+      std::string(util::SecretDataAsStringView(private_key_subtle.crt)));
 
   // Inner RSA public key.
   RsaSsaPssPublicKey* public_key_proto = private_key_proto.mutable_public_key();
@@ -134,12 +141,18 @@
 
   // RSA Private key parameters.
   private_key_proto.set_version(key_version);
-  private_key_proto.set_d(private_key_subtle.d);
-  private_key_proto.set_p(private_key_subtle.p);
-  private_key_proto.set_q(private_key_subtle.q);
-  private_key_proto.set_dp(private_key_subtle.dp);
-  private_key_proto.set_dq(private_key_subtle.dq);
-  private_key_proto.set_crt(private_key_subtle.crt);
+  private_key_proto.set_d(
+      std::string(util::SecretDataAsStringView(private_key_subtle.d)));
+  private_key_proto.set_p(
+      std::string(util::SecretDataAsStringView(private_key_subtle.p)));
+  private_key_proto.set_q(
+      std::string(util::SecretDataAsStringView(private_key_subtle.q)));
+  private_key_proto.set_dp(
+      std::string(util::SecretDataAsStringView(private_key_subtle.dp)));
+  private_key_proto.set_dq(
+      std::string(util::SecretDataAsStringView(private_key_subtle.dq)));
+  private_key_proto.set_crt(
+      std::string(util::SecretDataAsStringView(private_key_subtle.crt)));
 
   // Inner RSA Public key parameters.
   RsaSsaPkcs1PublicKey* public_key_proto =
@@ -315,6 +328,8 @@
           new PublicKeyVerifyPemKeysetReader(pem_serialized_keys_));
     }
   }
+  return util::Status(util::error::INVALID_ARGUMENT,
+                      "Unknown pem_reader_type_");
 }
 
 util::StatusOr<std::unique_ptr<Keyset>> PublicKeySignPemKeysetReader::Read() {
diff --git a/cc/signature/signature_pem_keyset_reader_test.cc b/cc/signature/signature_pem_keyset_reader_test.cc
index c7dcaed..dea6344 100644
--- a/cc/signature/signature_pem_keyset_reader_test.cc
+++ b/cc/signature/signature_pem_keyset_reader_test.cc
@@ -31,6 +31,7 @@
 #include "tink/subtle/pem_parser_boringssl.h"
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/enums.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/test_matchers.h"
 #include "proto/common.pb.h"
@@ -141,12 +142,18 @@
   RsaSsaPssPrivateKey private_key_proto;
 
   private_key_proto.set_version(key_version);
-  private_key_proto.set_d(key_subtle->d);
-  private_key_proto.set_p(key_subtle->p);
-  private_key_proto.set_q(key_subtle->q);
-  private_key_proto.set_dp(key_subtle->dp);
-  private_key_proto.set_dq(key_subtle->dq);
-  private_key_proto.set_crt(key_subtle->crt);
+  private_key_proto.set_d(
+      std::string(util::SecretDataAsStringView(key_subtle->d)));
+  private_key_proto.set_p(
+      std::string(util::SecretDataAsStringView(key_subtle->p)));
+  private_key_proto.set_q(
+      std::string(util::SecretDataAsStringView(key_subtle->q)));
+  private_key_proto.set_dp(
+      std::string(util::SecretDataAsStringView(key_subtle->dp)));
+  private_key_proto.set_dq(
+      std::string(util::SecretDataAsStringView(key_subtle->dq)));
+  private_key_proto.set_crt(
+      std::string(util::SecretDataAsStringView(key_subtle->crt)));
 
   // Set public key parameters.
   RsaSsaPssPublicKey* public_key_proto = private_key_proto.mutable_public_key();
diff --git a/cc/subtle/BUILD.bazel b/cc/subtle/BUILD.bazel
index 0cb486e..9d107ed 100644
--- a/cc/subtle/BUILD.bazel
+++ b/cc/subtle/BUILD.bazel
@@ -254,6 +254,7 @@
         "//util:status",
         "//util:statusor",
         "@boringssl//:crypto",
+        "@com_google_absl//absl/memory",
         "@com_google_absl//absl/strings",
     ],
 )
@@ -288,6 +289,7 @@
         "//util:status",
         "//util:statusor",
         "@boringssl//:crypto",
+        "@com_google_absl//absl/memory",
         "@com_google_absl//absl/strings",
     ],
 )
@@ -302,6 +304,7 @@
         ":subtle_util",
         ":subtle_util_boringssl",
         "//:aead",
+        "//config:tink_fips",
         "//util:errors",
         "//util:secret_data",
         "//util:status",
@@ -421,6 +424,7 @@
         ":subtle_util",
         ":subtle_util_boringssl",
         "//:aead",
+        "//config:tink_fips",
         "//util:errors",
         "//util:secret_data",
         "//util:status",
@@ -463,6 +467,7 @@
         ":random",
         ":subtle_util",
         ":subtle_util_boringssl",
+        "//config:tink_fips",
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
@@ -494,6 +499,7 @@
         ":subtle_util",
         ":subtle_util_boringssl",
         "//:aead",
+        "//config:tink_fips",
         "//util:errors",
         "//util:secret_data",
         "//util:status",
@@ -534,6 +540,7 @@
         ":random",
         ":subtle_util",
         "//:aead",
+        "//config:tink_fips",
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
@@ -566,6 +573,7 @@
     include_prefix = "tink/subtle",
     deps = [
         ":common_enums",
+        "//config:tink_fips",
         "//util:errors",
         "//util:secret_data",
         "//util:status",
@@ -683,6 +691,7 @@
     deps = [
         ":test_util",
         "//:aead",
+        "//aead:cord_aead",
         "//util:status",
         "@com_google_absl//absl/strings",
     ],
@@ -916,9 +925,11 @@
         ":aes_gcm_boringssl",
         ":wycheproof_util",
         "//:aead",
+        "//config:tink_fips",
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
+        "//util:test_matchers",
         "//util:test_util",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
@@ -1025,6 +1036,7 @@
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
+        "//util:test_matchers",
         "//util:test_util",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
@@ -1065,6 +1077,7 @@
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
+        "//util:test_matchers",
         "//util:test_util",
         "@com_google_googletest//:gtest_main",
     ],
@@ -1086,6 +1099,7 @@
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
+        "//util:test_matchers",
         "//util:test_util",
         "@com_google_absl//absl/strings",
         "@com_google_googletest//:gtest_main",
@@ -1275,6 +1289,7 @@
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
+        "//util:test_matchers",
         "//util:test_util",
         "@boringssl//:crypto",
         "@com_google_absl//absl/strings",
@@ -1331,6 +1346,7 @@
         ":ec_util",
         ":subtle_util_boringssl",
         ":wycheproof_util",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
@@ -1353,6 +1369,7 @@
         "//util:secret_data",
         "//util:status",
         "//util:statusor",
+        "//util:test_matchers",
         "//util:test_util",
         "@boringssl//:crypto",
         "@com_google_absl//absl/strings",
@@ -1521,6 +1538,7 @@
     deps = [
         ":pem_parser_boringssl",
         ":subtle_util_boringssl",
+        "//util:secret_data",
         "//util:status",
         "//util:statusor",
         "//util:test_matchers",
diff --git a/cc/subtle/CMakeLists.txt b/cc/subtle/CMakeLists.txt
index 5bffed8..3fb416f 100644
--- a/cc/subtle/CMakeLists.txt
+++ b/cc/subtle/CMakeLists.txt
@@ -243,6 +243,7 @@
     tink::util::status
     tink::util::statusor
     crypto
+    absl::memory
     absl::strings
 )
 
@@ -275,6 +276,7 @@
     tink::util::status
     tink::util::statusor
     crypto
+    absl::memory
     absl::strings
 )
 
@@ -284,6 +286,7 @@
     aes_gcm_boringssl.cc
     aes_gcm_boringssl.h
   DEPS
+    tink::config::tink_fips
     tink::subtle::random
     tink::subtle::subtle_util
     tink::subtle::subtle_util_boringssl
@@ -396,6 +399,7 @@
     aes_eax_boringssl.cc
     aes_eax_boringssl.h
   DEPS
+    tink::config::tink_fips
     tink::subtle::random
     tink::subtle::subtle_util
     tink::subtle::subtle_util_boringssl
@@ -436,6 +440,7 @@
     aes_ctr_boringssl.cc
     aes_ctr_boringssl.h
   DEPS
+    tink::config::tink_fips
     tink::subtle::ind_cpa_cipher
     tink::subtle::random
     tink::subtle::subtle_util
@@ -463,6 +468,7 @@
     xchacha20_poly1305_boringssl.cc
     xchacha20_poly1305_boringssl.h
   DEPS
+    tink::config::tink_fips
     tink::subtle::common_enums
     tink::subtle::random
     tink::subtle::subtle_util
@@ -501,6 +507,7 @@
     aes_gcm_siv_boringssl.cc
     aes_gcm_siv_boringssl.h
   DEPS
+    tink::config::tink_fips
     tink::subtle::random
     tink::subtle::subtle_util
     tink::core::aead
@@ -534,6 +541,7 @@
     subtle_util_boringssl.cc
     subtle_util_boringssl.h
   DEPS
+    tink::config::tink_fips
     tink::subtle::common_enums
     tink::util::errors
     tink::util::secret_data
@@ -638,6 +646,7 @@
   DEPS
     absl::strings
     tink::core::aead
+    tink::aead::cord_aead
     tink::util::status
 )
 
@@ -831,10 +840,12 @@
   DEPS
     tink::subtle::aes_gcm_boringssl
     tink::subtle::wycheproof_util
+    tink::config::tink_fips
     tink::core::aead
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
+    tink::util::test_matchers
     tink::util::test_util
     absl::strings
     gmock
@@ -923,6 +934,7 @@
     tink::core::aead
     tink::util::secret_data
     tink::util::status
+    tink::util::test_matchers
     tink::util::statusor
     tink::util::test_util
     absl::strings
@@ -956,6 +968,7 @@
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
+    tink::util::test_matchers
     tink::util::test_util
 )
 
@@ -971,6 +984,7 @@
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
+    tink::util::test_matchers
     tink::util::test_util
     absl::strings
     rapidjson
@@ -1118,6 +1132,7 @@
     crypto
     absl::strings
     rapidjson
+    gmock
 )
 
 tink_cc_test(
@@ -1152,6 +1167,7 @@
     tink::subtle::ec_util
     tink::subtle::subtle_util_boringssl
     tink::subtle::wycheproof_util
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::test_matchers
@@ -1170,6 +1186,7 @@
     tink::util::secret_data
     tink::util::status
     tink::util::statusor
+    tink::util::test_matchers
     tink::util::test_util
     crypto
     absl::strings
@@ -1316,6 +1333,7 @@
   DEPS
     tink::subtle::pem_parser_boringssl
     tink::subtle::subtle_util_boringssl
+    tink::util::secret_data
     tink::util::status
     tink::util::statusor
     tink::util::test_util
diff --git a/cc/subtle/aead_test_util.cc b/cc/subtle/aead_test_util.cc
index df123d1..86f7d16 100644
--- a/cc/subtle/aead_test_util.cc
+++ b/cc/subtle/aead_test_util.cc
@@ -23,13 +23,33 @@
 
 using ::crypto::tink::util::StatusOr;
 
-crypto::tink::util::Status EncryptThenDecrypt(Aead* encrypter, Aead* decrypter,
+crypto::tink::util::Status EncryptThenDecrypt(const Aead& encrypter,
+                                              const Aead& decrypter,
                                               absl::string_view message,
                                               absl::string_view aad) {
-  StatusOr<std::string> encryption_or = encrypter->Encrypt(message, aad);
+  StatusOr<std::string> encryption_or = encrypter.Encrypt(message, aad);
   if (!encryption_or.status().ok()) return encryption_or.status();
   StatusOr<std::string> decryption_or =
-      decrypter->Decrypt(encryption_or.ValueOrDie(), aad);
+      decrypter.Decrypt(encryption_or.ValueOrDie(), aad);
+  if (!decryption_or.status().ok()) return decryption_or.status();
+  if (decryption_or.ValueOrDie() != message) {
+    return crypto::tink::util::Status(crypto::tink::util::error::INTERNAL,
+                                      "Message/Decryption mismatch");
+  }
+  return util::OkStatus();
+}
+
+crypto::tink::util::Status EncryptThenDecrypt(const CordAead& encrypter,
+                                              const CordAead& decrypter,
+                                              absl::string_view message,
+                                              absl::string_view aad) {
+  absl::Cord message_cord = absl::Cord(message);
+  absl::Cord aad_cord = absl::Cord(aad);
+  StatusOr<absl::Cord> encryption_or =
+      encrypter.Encrypt(message_cord, aad_cord);
+  if (!encryption_or.status().ok()) return encryption_or.status();
+  StatusOr<absl::Cord> decryption_or =
+      decrypter.Decrypt(encryption_or.ValueOrDie(), aad_cord);
   if (!decryption_or.status().ok()) return decryption_or.status();
   if (decryption_or.ValueOrDie() != message) {
     return crypto::tink::util::Status(crypto::tink::util::error::INTERNAL,
diff --git a/cc/subtle/aead_test_util.h b/cc/subtle/aead_test_util.h
index 541f74f..c4fec32 100644
--- a/cc/subtle/aead_test_util.h
+++ b/cc/subtle/aead_test_util.h
@@ -16,6 +16,7 @@
 
 #include "absl/strings/string_view.h"
 #include "tink/aead.h"
+#include "tink/aead/cord_aead.h"
 #include "tink/util/status.h"
 
 namespace crypto {
@@ -23,7 +24,15 @@
 
 // Encrypt, then decrypt. Any error will be propagated to the caller. Returns OK
 // if the resulting decryption is equal to the plaintext.
-crypto::tink::util::Status EncryptThenDecrypt(Aead* encrypter, Aead* decrypter,
+crypto::tink::util::Status EncryptThenDecrypt(const Aead& encrypter,
+                                              const Aead& decrypter,
+                                              absl::string_view message,
+                                              absl::string_view aad);
+
+// Encrypt, then decrypt. Any error will be propagated to the caller. Returns OK
+// if the resulting decryption is equal to the plaintext.
+crypto::tink::util::Status EncryptThenDecrypt(const CordAead& encrypter,
+                                              const CordAead& decrypter,
                                               absl::string_view message,
                                               absl::string_view aad);
 
diff --git a/cc/subtle/aead_test_util_test.cc b/cc/subtle/aead_test_util_test.cc
index a78c4a8..2b4b0bd 100644
--- a/cc/subtle/aead_test_util_test.cc
+++ b/cc/subtle/aead_test_util_test.cc
@@ -28,13 +28,13 @@
 
 TEST(EncryptThenDecrypt, Basic) {
   test::DummyAead aead("Aead 1");
-  EXPECT_THAT(EncryptThenDecrypt(&aead, &aead, "plaintext", "aad"), IsOk());
+  EXPECT_THAT(EncryptThenDecrypt(aead, aead, "plaintext", "aad"), IsOk());
 }
 
 TEST(EncryptThenDecrypt, DifferentAeads) {
   test::DummyAead aead_1("Aead 1");
   test::DummyAead aead_2("Aead 2");
-  EXPECT_THAT(EncryptThenDecrypt(&aead_1, &aead_2, "plaintext", "aad"),
+  EXPECT_THAT(EncryptThenDecrypt(aead_1, aead_2, "plaintext", "aad"),
               Not(IsOk()));
 }
 
diff --git a/cc/subtle/aes_cmac_boringssl.cc b/cc/subtle/aes_cmac_boringssl.cc
index e7329c0..4a9dce1 100644
--- a/cc/subtle/aes_cmac_boringssl.cc
+++ b/cc/subtle/aes_cmac_boringssl.cc
@@ -20,6 +20,7 @@
 
 #include "absl/memory/memory.h"
 #include "openssl/cmac.h"
+#include "openssl/mem.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/status.h"
@@ -76,11 +77,7 @@
     return util::Status(util::error::INTERNAL,
                         "BoringSSL failed to compute CMAC");
   }
-  uint8_t diff = 0;
-  for (uint32_t i = 0; i < tag_size_; i++) {
-    diff |= buf[i] ^ static_cast<uint8_t>(mac[i]);
-  }
-  if (diff != 0) {
+  if (CRYPTO_memcmp(buf, mac.data(), tag_size_) != 0) {
     return util::Status(util::error::INVALID_ARGUMENT, "verification failed");
   }
   return util::OkStatus();
diff --git a/cc/subtle/aes_ctr_boringssl.cc b/cc/subtle/aes_ctr_boringssl.cc
index cc89cea..4cdb8b4 100644
--- a/cc/subtle/aes_ctr_boringssl.cc
+++ b/cc/subtle/aes_ctr_boringssl.cc
@@ -20,6 +20,7 @@
 
 #include "absl/memory/memory.h"
 #include "openssl/evp.h"
+#include "tink/config/tink_fips.h"
 #include "tink/subtle/random.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/subtle/subtle_util_boringssl.h"
@@ -31,6 +32,9 @@
 
 util::StatusOr<std::unique_ptr<IndCpaCipher>> AesCtrBoringSsl::New(
     util::SecretData key, int iv_size) {
+  auto status = CheckFipsCompatibility<AesCtrBoringSsl>();
+  if (!status.ok()) return status;
+
   const EVP_CIPHER* cipher =
       SubtleUtilBoringSSL::GetAesCtrCipherForKeySize(key.size());
   if (cipher == nullptr) {
diff --git a/cc/subtle/aes_ctr_boringssl.h b/cc/subtle/aes_ctr_boringssl.h
index f49b6bb..f6832a2 100644
--- a/cc/subtle/aes_ctr_boringssl.h
+++ b/cc/subtle/aes_ctr_boringssl.h
@@ -21,6 +21,7 @@
 #include <utility>
 
 #include "openssl/evp.h"
+#include "tink/config/tink_fips.h"
 #include "tink/subtle/ind_cpa_cipher.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/statusor.h"
@@ -40,6 +41,9 @@
   crypto::tink::util::StatusOr<std::string> Decrypt(
       absl::string_view ciphertext) const override;
 
+  static constexpr crypto::tink::FipsCompatibility kFipsStatus =
+      crypto::tink::FipsCompatibility::kRequiresBoringCrypto;
+
  private:
   static constexpr int kMinIvSizeInBytes = 12;
   static constexpr int kBlockSize = 16;
diff --git a/cc/subtle/aes_ctr_boringssl_test.cc b/cc/subtle/aes_ctr_boringssl_test.cc
index 9cac0e0..4c2f66f 100644
--- a/cc/subtle/aes_ctr_boringssl_test.cc
+++ b/cc/subtle/aes_ctr_boringssl_test.cc
@@ -24,6 +24,7 @@
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 
 namespace crypto {
@@ -31,7 +32,15 @@
 namespace subtle {
 namespace {
 
-TEST(AesCtrBoringSslTest, testEncryptDecrypt) {
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
+
+TEST(AesCtrBoringSslTest, TestEncryptDecrypt) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   int iv_size = 12;
@@ -47,7 +56,12 @@
   EXPECT_EQ(pt.ValueOrDie(), message);
 }
 
-TEST(AesCtrBoringSslTest, testEncryptDecrypt_randomMessage) {
+TEST(AesCtrBoringSslTest, TestEncryptDecrypt_randomMessage) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   int iv_size = 12;
@@ -65,7 +79,12 @@
   }
 }
 
-TEST(AesCtrBoringSslTest, testEncryptDecrypt_randomKey_randomMessage) {
+TEST(AesCtrBoringSslTest, TestEncryptDecrypt_randomKey_randomMessage) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   for (int i = 0; i < 256; i++) {
     util::SecretData key = Random::GetRandomKeyBytes(16);
     int iv_size = 12;
@@ -82,7 +101,12 @@
   }
 }
 
-TEST(AesCtrBoringSslTest, testEncryptDecrypt_invalidIvSize) {
+TEST(AesCtrBoringSslTest, TestEncryptDecrypt_invalidIvSize) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   int iv_size = 11;
@@ -94,7 +118,12 @@
   EXPECT_FALSE(res2.ok()) << res2.status();
 }
 
-TEST(AesCtrBoringSslTest, testNistTestVector) {
+TEST(AesCtrBoringSslTest, TestNistTestVector) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   // NIST SP 800-38A pp 55.
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("2b7e151628aed2a6abf7158809cf4f3c"));
@@ -110,7 +139,12 @@
   EXPECT_EQ(pt.ValueOrDie(), message);
 }
 
-TEST(AesCtrBoringSslTest, testMultipleEncrypt) {
+TEST(AesCtrBoringSslTest, TestMultipleEncrypt) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   util::SecretData key = Random::GetRandomKeyBytes(16);
   int iv_size = 12;
   auto res = AesCtrBoringSsl::New(key, iv_size);
@@ -122,6 +156,38 @@
   EXPECT_NE(ct1.ValueOrDie(), ct2.ValueOrDie());
 }
 
+TEST(AesCtrBoringSslTest, TestFipsOnly) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
+  util::SecretData key128 = util::SecretDataFromStringView(
+      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  util::SecretData key256 = util::SecretDataFromStringView(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+
+  EXPECT_THAT(subtle::AesCtrBoringSsl::New(key128, 16).status(), IsOk());
+  EXPECT_THAT(subtle::AesCtrBoringSsl::New(key256, 16).status(), IsOk());
+}
+
+TEST(AesCtrBoringSslTest, TestFipsFailWithoutBoringCrypto) {
+  if (!kUseOnlyFips || FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
+  }
+
+  util::SecretData key128 = util::SecretDataFromStringView(
+      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  util::SecretData key256 = util::SecretDataFromStringView(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+
+  EXPECT_THAT(subtle::AesCtrBoringSsl::New(key128, 16).status(),
+              StatusIs(util::error::INTERNAL));
+  EXPECT_THAT(subtle::AesCtrBoringSsl::New(key256, 16).status(),
+              StatusIs(util::error::INTERNAL));
+}
+
 }  // namespace
 }  // namespace subtle
 }  // namespace tink
diff --git a/cc/subtle/aes_eax_boringssl.cc b/cc/subtle/aes_eax_boringssl.cc
index 5fea7c7..60b7c4b 100644
--- a/cc/subtle/aes_eax_boringssl.cc
+++ b/cc/subtle/aes_eax_boringssl.cc
@@ -28,6 +28,7 @@
 #include "openssl/err.h"
 #include "openssl/evp.h"
 #include "tink/aead.h"
+#include "tink/config/tink_fips.h"
 #include "tink/subtle/random.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/subtle/subtle_util_boringssl.h"
@@ -104,9 +105,9 @@
   uint64_t in_low = BigEndianLoad64(in + 8);
   uint64_t out_high = (in_high << 1) ^ (in_low >> 63);
   // If the most significant bit is set then the result has to
-  // be reduced by x^128 + x^7 + x^4 + x^2 + x + 1.
-  // The representation of x^7 + x^4 + x^2 + x + 1 is 0x87.
-  uint64_t out_low = (in_low << 1) ^ (in_high >> 63 ? 0x87 : 0);
+  // be reduced by x^128 + x^7 + x^2 + x + 1.
+  // The representation of x^7 + x^2 + x + 1 is 0x87.
+  uint64_t out_low = (in_low << 1) ^ (0x87 & -(in_high >> 63));
   BigEndianStore64(out_high, out);
   BigEndianStore64(out_low, out + 8);
 }
@@ -143,6 +144,9 @@
 
 crypto::tink::util::StatusOr<std::unique_ptr<Aead>> AesEaxBoringSsl::New(
     const util::SecretData& key, size_t nonce_size_in_bytes) {
+  auto status = CheckFipsCompatibility<AesEaxBoringSsl>();
+  if (!status.ok()) return status;
+
   if (!IsValidKeySize(key.size())) {
     return util::Status(util::error::INVALID_ARGUMENT, "Invalid key size");
   }
diff --git a/cc/subtle/aes_eax_boringssl.h b/cc/subtle/aes_eax_boringssl.h
index 9ad825f..2f4263c 100644
--- a/cc/subtle/aes_eax_boringssl.h
+++ b/cc/subtle/aes_eax_boringssl.h
@@ -24,6 +24,7 @@
 #include "absl/types/span.h"
 #include "openssl/aes.h"
 #include "openssl/evp.h"
+#include "tink/config/tink_fips.h"
 #include "tink/aead.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
@@ -50,6 +51,9 @@
       absl::string_view ciphertext,
       absl::string_view additional_data) const override;
 
+  static constexpr crypto::tink::FipsCompatibility kFipsStatus =
+      crypto::tink::FipsCompatibility::kNotFips;
+
  private:
   static constexpr int kTagSize = 16;
   static constexpr int kBlockSize = 16;
diff --git a/cc/subtle/aes_eax_boringssl_test.cc b/cc/subtle/aes_eax_boringssl_test.cc
index e1fd3a0..b9cce21 100644
--- a/cc/subtle/aes_eax_boringssl_test.cc
+++ b/cc/subtle/aes_eax_boringssl_test.cc
@@ -26,6 +26,7 @@
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 
 namespace crypto {
@@ -33,7 +34,13 @@
 namespace subtle {
 namespace {
 
-TEST(AesEaxBoringSslTest, testBasic) {
+using ::crypto::tink::test::StatusIs;
+
+TEST(AesEaxBoringSslTest, TestBasic) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   size_t nonce_size = 12;
@@ -50,7 +57,11 @@
   EXPECT_EQ(pt.ValueOrDie(), message);
 }
 
-TEST(AesEaxBoringSslTest, testMessageSize) {
+TEST(AesEaxBoringSslTest, TestMessageSize) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   size_t nonce_size = 12;
@@ -69,7 +80,11 @@
   }
 }
 
-TEST(AesEaxBoringSslTest, testAadSize) {
+TEST(AesEaxBoringSslTest, TestAadSize) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   size_t nonce_size = 12;
@@ -88,7 +103,11 @@
   }
 }
 
-TEST(AesEaxBoringSslTest, testLongNonce) {
+TEST(AesEaxBoringSslTest, TestLongNonce) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   size_t nonce_size = 16;
@@ -105,7 +124,11 @@
   EXPECT_EQ(pt.ValueOrDie(), message);
 }
 
-TEST(AesEaxBoringSslTest, testModification) {
+TEST(AesEaxBoringSslTest, TestModification) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   size_t nonce_size = 12;
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
@@ -134,7 +157,11 @@
   }
 }
 
-TEST(AesEaxBoringSslTest, testInvalidKeySizes) {
+TEST(AesEaxBoringSslTest, TestInvalidKeySizes) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   size_t nonce_size = 12;
   for (int keysize = 0; keysize < 65; keysize++) {
     if (keysize == 16 || keysize == 32) {
@@ -146,7 +173,11 @@
   }
 }
 
-TEST(AesEaxBoringSslTest, testEmpty) {
+TEST(AesEaxBoringSslTest, TestEmpty) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   size_t nonce_size = 12;
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("bedcfb5a011ebc84600fcb296c15af0d"));
@@ -279,11 +310,31 @@
 }
 
 TEST(AesEaxBoringSslTest, TestVectors) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   std::unique_ptr<rapidjson::Document> root =
       WycheproofUtil::ReadTestVectors("aes_eax_test.json");
   ASSERT_TRUE(WycheproofTest(*root));
 }
 
+TEST(AesEaxBoringSslTest, TestFipsOnly) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  util::SecretData key128 = util::SecretDataFromStringView(
+      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  util::SecretData key256 = util::SecretDataFromStringView(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+
+  EXPECT_THAT(subtle::AesEaxBoringSsl::New(key128, 16).status(),
+              StatusIs(util::error::INTERNAL));
+  EXPECT_THAT(subtle::AesEaxBoringSsl::New(key256, 16).status(),
+              StatusIs(util::error::INTERNAL));
+}
+
 }  // namespace
 }  // namespace subtle
 }  // namespace tink
diff --git a/cc/subtle/aes_gcm_boringssl.cc b/cc/subtle/aes_gcm_boringssl.cc
index b0e5b9b..7991410 100644
--- a/cc/subtle/aes_gcm_boringssl.cc
+++ b/cc/subtle/aes_gcm_boringssl.cc
@@ -20,6 +20,7 @@
 
 #include "absl/memory/memory.h"
 #include "openssl/aead.h"
+#include "tink/config/tink_fips.h"
 #include "tink/subtle/random.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/subtle/subtle_util_boringssl.h"
@@ -31,6 +32,9 @@
 
 util::StatusOr<std::unique_ptr<Aead>> AesGcmBoringSsl::New(
     const util::SecretData& key) {
+  auto status = CheckFipsCompatibility<AesGcmBoringSsl>();
+  if (!status.ok()) return status;
+
   const EVP_AEAD* aead =
       SubtleUtilBoringSSL::GetAesGcmAeadForKeySize(key.size());
   if (aead == nullptr) {
diff --git a/cc/subtle/aes_gcm_boringssl.h b/cc/subtle/aes_gcm_boringssl.h
index ecfa2f0..fbe579f 100644
--- a/cc/subtle/aes_gcm_boringssl.h
+++ b/cc/subtle/aes_gcm_boringssl.h
@@ -23,6 +23,7 @@
 #include "absl/base/macros.h"
 #include "openssl/aead.h"
 #include "tink/aead.h"
+#include "tink/config/tink_fips.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/statusor.h"
 
@@ -48,6 +49,9 @@
       absl::string_view ciphertext,
       absl::string_view additional_data) const override;
 
+  static constexpr crypto::tink::FipsCompatibility kFipsStatus =
+      crypto::tink::FipsCompatibility::kRequiresBoringCrypto;
+
  private:
   static constexpr int kIvSizeInBytes = 12;
   static constexpr int kTagSizeInBytes = 16;
diff --git a/cc/subtle/aes_gcm_boringssl_test.cc b/cc/subtle/aes_gcm_boringssl_test.cc
index 262738f..5506a39 100644
--- a/cc/subtle/aes_gcm_boringssl_test.cc
+++ b/cc/subtle/aes_gcm_boringssl_test.cc
@@ -24,10 +24,12 @@
 #include "absl/strings/str_cat.h"
 #include "openssl/err.h"
 #include "include/rapidjson/document.h"
+#include "tink/config/tink_fips.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"
 #include "tink/util/test_util.h"
 
 namespace crypto {
@@ -35,9 +37,16 @@
 namespace subtle {
 namespace {
 
+using ::crypto::tink::test::IsOk;
+using ::crypto::tink::test::StatusIs;
 using ::testing::Eq;
 
 TEST(AesGcmBoringSslTest, testBasic) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto res = AesGcmBoringSsl::New(key);
@@ -54,6 +63,11 @@
 }
 
 TEST(AesGcmBoringSslTest, testModification) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto cipher = std::move(AesGcmBoringSsl::New(key).ValueOrDie());
@@ -83,6 +97,11 @@
 
 void TestDecryptWithEmptyAad(crypto::tink::Aead* cipher, absl::string_view ct,
                              absl::string_view message) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   {  // AAD is a null string_view.
     const absl::string_view aad;
     auto pt_or_status = cipher->Decrypt(ct, aad);
@@ -105,6 +124,11 @@
 }
 
 TEST(AesGcmBoringSslTest, testAadEmptyVersusNullStringView) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   const util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto cipher = std::move(AesGcmBoringSsl::New(key).ValueOrDie());
@@ -133,6 +157,11 @@
 }
 
 TEST(AesGcmBoringSslTest, testMessageEmptyVersusNullStringView) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   const util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto cipher = std::move(AesGcmBoringSsl::New(key).ValueOrDie());
@@ -169,6 +198,11 @@
 }
 
 TEST(AesGcmBoringSslTest, testBothMessageAndAadEmpty) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   const util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto cipher = std::move(AesGcmBoringSsl::New(key).ValueOrDie());
@@ -206,6 +240,11 @@
 }
 
 TEST(AesGcmBoringSslTest, testInvalidKeySizes) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   for (int keysize = 0; keysize < 65; keysize++) {
     util::SecretData key(keysize, 'x');
     auto cipher = AesGcmBoringSsl::New(key);
@@ -279,11 +318,48 @@
 }
 
 TEST(AesGcmBoringSslTest, TestVectors) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
   std::unique_ptr<rapidjson::Document> root =
       WycheproofUtil::ReadTestVectors("aes_gcm_test.json");
   ASSERT_TRUE(WycheproofTest(*root));
 }
 
+TEST(AesGcmBoringSslTest, TestFipsOnly) {
+  if (kUseOnlyFips && !FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test should not run in FIPS mode when BoringCrypto is unavailable.";
+  }
+
+  util::SecretData key128 = util::SecretDataFromStringView(
+      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  util::SecretData key256 = util::SecretDataFromStringView(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+
+  EXPECT_THAT(subtle::AesGcmBoringSsl::New(key128).status(), IsOk());
+  EXPECT_THAT(subtle::AesGcmBoringSsl::New(key256).status(), IsOk());
+}
+
+TEST(AesGcmBoringSslTest, TestFipsFailWithoutBoringCrypto) {
+  if (!kUseOnlyFips || FIPS_mode()) {
+    GTEST_SKIP()
+        << "Test assumes kOnlyUseFips but BoringCrypto is unavailable.";
+  }
+
+  util::SecretData key128 = util::SecretDataFromStringView(
+      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  util::SecretData key256 = util::SecretDataFromStringView(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+
+  EXPECT_THAT(subtle::AesGcmBoringSsl::New(key128).status(),
+              StatusIs(util::error::INTERNAL));
+  EXPECT_THAT(subtle::AesGcmBoringSsl::New(key256).status(),
+              StatusIs(util::error::INTERNAL));
+}
+
 }  // namespace
 }  // namespace subtle
 }  // namespace tink
diff --git a/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.cc b/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.cc
index efa4fce..b9a978a 100644
--- a/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.cc
+++ b/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.cc
@@ -144,9 +144,10 @@
   if (aead == nullptr) {
     return util::Status(util::error::INTERNAL, "invalid key size");
   }
-  if (EVP_AEAD_CTX_init(ctx_.get(), aead, key.data(), key.size(),
-                        AesGcmHkdfStreamSegmentEncrypter::kTagSizeInBytes,
-                        nullptr) != 1) {
+  ctx_.reset(
+      EVP_AEAD_CTX_new(aead, key.data(), key.size(),
+                       AesGcmHkdfStreamSegmentEncrypter::kTagSizeInBytes));
+  if (!ctx_) {
     return util::Status(util::error::INTERNAL,
                         "could not initialize EVP_AEAD_CTX");
   }
diff --git a/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.h b/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.h
index 519d304..b553ce3 100644
--- a/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.h
+++ b/cc/subtle/aes_gcm_hkdf_stream_segment_decrypter.h
@@ -112,7 +112,7 @@
   bool is_initialized_ = false;
   std::vector<uint8_t> salt_;
   std::vector<uint8_t> nonce_prefix_;
-  bssl::ScopedEVP_AEAD_CTX ctx_;
+  bssl::UniquePtr<EVP_AEAD_CTX> ctx_;
 };
 
 }  // namespace subtle
diff --git a/cc/subtle/aes_gcm_siv_boringssl.cc b/cc/subtle/aes_gcm_siv_boringssl.cc
index d2bfaec..9b8d699 100644
--- a/cc/subtle/aes_gcm_siv_boringssl.cc
+++ b/cc/subtle/aes_gcm_siv_boringssl.cc
@@ -21,6 +21,7 @@
 
 #include "absl/memory/memory.h"
 #include "openssl/aead.h"
+#include "tink/config/tink_fips.h"
 #include "tink/subtle/random.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/util/status.h"
@@ -44,6 +45,9 @@
 
 util::StatusOr<std::unique_ptr<Aead>> AesGcmSivBoringSsl::New(
     const util::SecretData& key) {
+  auto status = CheckFipsCompatibility<AesGcmSivBoringSsl>();
+  if (!status.ok()) return status;
+
   const EVP_AEAD* aead = GetCipherForKeySize(key.size());
   if (aead == nullptr) {
     return util::Status(util::error::INVALID_ARGUMENT, "invalid key size");
diff --git a/cc/subtle/aes_gcm_siv_boringssl.h b/cc/subtle/aes_gcm_siv_boringssl.h
index e46894a..7257075 100644
--- a/cc/subtle/aes_gcm_siv_boringssl.h
+++ b/cc/subtle/aes_gcm_siv_boringssl.h
@@ -23,6 +23,7 @@
 #include "absl/strings/string_view.h"
 #include "openssl/aead.h"
 #include "tink/aead.h"
+#include "tink/config/tink_fips.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/statusor.h"
 
@@ -60,6 +61,9 @@
       absl::string_view ciphertext,
       absl::string_view additional_data) const override;
 
+  static constexpr crypto::tink::FipsCompatibility kFipsStatus =
+      crypto::tink::FipsCompatibility::kNotFips;
+
  private:
   static constexpr int kIvSizeInBytes = 12;
   static constexpr int kTagSizeInBytes = 16;
diff --git a/cc/subtle/aes_gcm_siv_boringssl_test.cc b/cc/subtle/aes_gcm_siv_boringssl_test.cc
index 9cd71f7..ef946b7 100644
--- a/cc/subtle/aes_gcm_siv_boringssl_test.cc
+++ b/cc/subtle/aes_gcm_siv_boringssl_test.cc
@@ -27,6 +27,7 @@
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 
 namespace crypto {
@@ -34,7 +35,13 @@
 namespace subtle {
 namespace {
 
+using ::crypto::tink::test::StatusIs;
+
 TEST(AesGcmSivBoringSslTest, Basic) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto res = AesGcmSivBoringSsl::New(key);
@@ -55,6 +62,10 @@
 }
 
 TEST(AesGcmSivBoringSslTest, Sizes) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto res = AesGcmSivBoringSsl::New(key);
@@ -84,6 +95,10 @@
 }
 
 TEST(AesGcmSivBoringSslTest, Modification) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto cipher = std::move(AesGcmSivBoringSsl::New(key).ValueOrDie());
@@ -114,6 +129,10 @@
 }
 
 TEST(AesGcmSivBoringSslTest, AadEmptyVersusNullStringView) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   const util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto cipher = std::move(AesGcmSivBoringSsl::New(key).ValueOrDie());
@@ -169,6 +188,10 @@
 }
 
 TEST(AesGcmSivBoringSslTest, MessageEmptyVersusNullStringView) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   const util::SecretData key = util::SecretDataFromStringView(
       test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
   auto cipher = std::move(AesGcmSivBoringSsl::New(key).ValueOrDie());
@@ -205,6 +228,10 @@
 }
 
 TEST(AesGcmSivBoringSslTest, InvalidKeySizes) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   for (int keysize = 0; keysize < 65; keysize++) {
     if (keysize == 16 || keysize == 32) {
       continue;
@@ -268,11 +295,31 @@
 }
 
 TEST(AesGcmSivBoringSslTest, TestVectors) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   std::unique_ptr<rapidjson::Document> root =
       WycheproofUtil::ReadTestVectors("aes_gcm_siv_test.json");
   ASSERT_TRUE(WycheproofTest(*root));
 }
 
+TEST(AesGcmSivBoringSslTest, TestFipsOnly) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  util::SecretData key128 = util::SecretDataFromStringView(
+      test::HexDecodeOrDie("000102030405060708090a0b0c0d0e0f"));
+  util::SecretData key256 = util::SecretDataFromStringView(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+
+  EXPECT_THAT(subtle::AesGcmSivBoringSsl::New(key128).status(),
+              StatusIs(util::error::INTERNAL));
+  EXPECT_THAT(subtle::AesGcmSivBoringSsl::New(key256).status(),
+              StatusIs(util::error::INTERNAL));
+}
+
 }  // namespace
 }  // namespace subtle
 }  // namespace tink
diff --git a/cc/subtle/aes_siv_boringssl.cc b/cc/subtle/aes_siv_boringssl.cc
index 422973a..1b174bc 100644
--- a/cc/subtle/aes_siv_boringssl.cc
+++ b/cc/subtle/aes_siv_boringssl.cc
@@ -21,6 +21,7 @@
 
 #include "absl/memory/memory.h"
 #include "openssl/aes.h"
+#include "openssl/mem.h"
 #include "tink/deterministic_aead.h"
 #include "tink/util/errors.h"
 #include "tink/util/status.h"
@@ -96,12 +97,13 @@
 
 // static
 void AesSivBoringSsl::MultiplyByX(uint8_t block[kBlockSize]) {
-  // Cast to signed makes the left shift produce either 0x00 or 0xff.
-  uint8_t carry = *reinterpret_cast<int8_t*>(&block[0]) >> 7;
+  // Carry over 0x87 if msb is 1 0x00 if msb is 0.
+  uint8_t carry = 0x87 & -(block[0] >> 7);
   for (size_t i = 0; i < kBlockSize - 1; ++i) {
     block[i] = (block[i] << 1) | (block[i + 1] >> 7);
   }
-  block[kBlockSize - 1] = (block[kBlockSize - 1] << 1) ^ (carry & 0x87);
+  block[kBlockSize - 1] =
+      (block[kBlockSize - 1] << 1) ^ carry;
 }
 
 // static
@@ -226,12 +228,7 @@
   S2v(absl::MakeSpan(reinterpret_cast<const uint8_t*>(additional_data.data()),
                      additional_data.size()),
       absl::MakeSpan(pt), s2v);
-  // Compare the siv from the ciphertext with the recomputed siv
-  uint8_t diff = 0;
-  for (int i = 0; i < kBlockSize; ++i) {
-    diff |= siv[i] ^ s2v[i];
-  }
-  if (diff != 0) {
+  if (CRYPTO_memcmp(siv, s2v, kBlockSize) != 0) {
     return util::Status(util::error::INVALID_ARGUMENT, "invalid ciphertext");
   }
   return std::string(reinterpret_cast<const char*>(pt.data()), plaintext_size);
diff --git a/cc/subtle/ecdsa_sign_boringssl.cc b/cc/subtle/ecdsa_sign_boringssl.cc
index 1c044c6..38c84a0 100644
--- a/cc/subtle/ecdsa_sign_boringssl.cc
+++ b/cc/subtle/ecdsa_sign_boringssl.cc
@@ -99,8 +99,7 @@
   }
 
   bssl::UniquePtr<BIGNUM> priv_key(
-      BN_bin2bn(reinterpret_cast<const unsigned char*>(ec_key.priv.data()),
-                ec_key.priv.size(), nullptr));
+      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(util::error::INVALID_ARGUMENT,
                         absl::StrCat("Invalid private key: ",
diff --git a/cc/subtle/ecdsa_sign_boringssl.h b/cc/subtle/ecdsa_sign_boringssl.h
index 03f1280..5c1e7a1 100644
--- a/cc/subtle/ecdsa_sign_boringssl.h
+++ b/cc/subtle/ecdsa_sign_boringssl.h
@@ -42,8 +42,6 @@
   crypto::tink::util::StatusOr<std::string> Sign(
       absl::string_view data) const override;
 
-  virtual ~EcdsaSignBoringSsl() {}
-
  private:
   EcdsaSignBoringSsl(bssl::UniquePtr<EC_KEY> key, const EVP_MD* hash,
                      EcdsaSignatureEncoding encoding);
diff --git a/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc b/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc
index a9efa63..70b3518 100644
--- a/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc
+++ b/cc/subtle/ecies_hkdf_sender_kem_boringssl_test.cc
@@ -86,8 +86,7 @@
     ASSERT_TRUE(status_or_kem_key.ok());
     auto kem_key = std::move(status_or_kem_key.ValueOrDie());
     auto ecies_recipient(
-        std::move(EciesHkdfRecipientKemBoringSsl::New(
-                      test.curve, util::SecretDataFromStringView(test_key.priv))
+        std::move(EciesHkdfRecipientKemBoringSsl::New(test.curve, test_key.priv)
                       .ValueOrDie()));
     auto status_or_shared_secret = ecies_recipient->GenerateKey(
         kem_key->get_kem_bytes(), test.hash,
diff --git a/cc/subtle/encrypt_then_authenticate.cc b/cc/subtle/encrypt_then_authenticate.cc
index 7bc5652..92678be 100644
--- a/cc/subtle/encrypt_then_authenticate.cc
+++ b/cc/subtle/encrypt_then_authenticate.cc
@@ -16,6 +16,7 @@
 
 #include "tink/subtle/encrypt_then_authenticate.h"
 
+#include <cstdint>
 #include <string>
 #include <vector>
 
@@ -66,7 +67,12 @@
   std::string ciphertext(ct.ValueOrDie());
   std::string toAuthData(additional_data);
   toAuthData.append(ciphertext);
-  uint64_t aad_size_in_bits = additional_data.size() * 8;
+  uint64_t aad_size_in_bytes = additional_data.size();
+  uint64_t aad_size_in_bits = aad_size_in_bytes * 8;
+  if (aad_size_in_bits / 8 != aad_size_in_bytes /* overflow occured! */) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "additional data too long");
+  }
   toAuthData.append(longToBigEndianStr(aad_size_in_bits));
   auto tag = mac_->ComputeMac(toAuthData);
   if (!tag.ok()) {
@@ -92,7 +98,12 @@
                             .substr(0, ciphertext.size() - tag_size_);
   std::string toAuthData(additional_data);
   toAuthData.append(payload);
-  uint64_t aad_size_in_bits = additional_data.size() * 8;
+  uint64_t aad_size_in_bytes = additional_data.size();
+  uint64_t aad_size_in_bits = aad_size_in_bytes * 8;
+  if (aad_size_in_bits / 8 != aad_size_in_bytes /* overflow occured! */) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "additional data too long");
+  }
   toAuthData.append(longToBigEndianStr(aad_size_in_bits));
   auto verified = mac_->VerifyMac(
       ciphertext.substr(ciphertext.size() - tag_size_, tag_size_), toAuthData);
diff --git a/cc/subtle/encrypt_then_authenticate_test.cc b/cc/subtle/encrypt_then_authenticate_test.cc
index 1b9dc45..153bbe3 100644
--- a/cc/subtle/encrypt_then_authenticate_test.cc
+++ b/cc/subtle/encrypt_then_authenticate_test.cc
@@ -265,6 +265,46 @@
   }
 }
 
+// EncryptThenAuthenticate computes the MAC over aad || ciphertext ||
+// aad_size_in_bits, where aad_size_in_bits = aad_size() * 8 [1].
+// aad.size() returns a size_t which is usually unsigned long or unsigned long
+// long. On 32-bit machines (and maybe others), long is 32-bit int. If
+// aad.size() returns a number equal to or larger than 2^29, an overflow will
+// occur when multiplying with 8 to get the size in bits. This leads to an
+// authentication bypass vulnerability. This 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;
+  int tag_size = 16;
+  auto cipher = std::move(createAead(encryption_key_size, iv_size, mac_key_size,
+                                     tag_size, HashType::SHA1)
+                              .ValueOrDie());
+
+  // Encrypt a message...
+  const std::string message = "Some data to encrypt.";
+  // ...with a long aad whose size in bits converted to an unsigned 32-bit
+  // integer is 0.
+  const std::string aad = std::string(1 << 29, 'a');
+  auto encrypted = cipher->Encrypt(message, aad);
+  EXPECT_TRUE(encrypted.ok()) << encrypted.status();
+  auto ct = encrypted.ValueOrDie();
+  auto decrypted = cipher->Decrypt(ct, aad);
+  EXPECT_TRUE(decrypted.ok()) << decrypted.status();
+
+  // Test that the 2^29-byte aad is NOT considered equal to an empty aad.
+  // That is, test that a valid tag for (ciphertext, aad) is INVALID for (aad
+  // + ciphertext, "").
+  ct = aad + ct;
+  decrypted = cipher->Decrypt(ct, "");
+  EXPECT_FALSE(decrypted.ok());
+#endif  // __ASYLO__
+}
+
 }  // namespace
 }  // namespace subtle
 }  // namespace tink
diff --git a/cc/subtle/hmac_boringssl.cc b/cc/subtle/hmac_boringssl.cc
index 063d530..6fd6c64 100644
--- a/cc/subtle/hmac_boringssl.cc
+++ b/cc/subtle/hmac_boringssl.cc
@@ -29,6 +29,7 @@
 #include "openssl/err.h"
 #include "openssl/evp.h"
 #include "openssl/hmac.h"
+#include "openssl/mem.h"
 
 
 namespace crypto {
@@ -96,11 +97,7 @@
     return util::Status(util::error::INTERNAL,
                         "BoringSSL failed to compute HMAC");
   }
-  uint8_t diff = 0;
-  for (uint32_t i = 0; i < tag_size_; i++) {
-    diff |= buf[i] ^ static_cast<uint8_t>(mac[i]);
-  }
-  if (diff != 0) {
+  if (CRYPTO_memcmp(buf, mac.data(), tag_size_) != 0) {
     return util::Status(util::error::INVALID_ARGUMENT, "verification failed");
   }
   return util::Status::OK;
diff --git a/cc/subtle/pem_parser_boringssl.cc b/cc/subtle/pem_parser_boringssl.cc
index 0063d1a..6e2d3b6 100644
--- a/cc/subtle/pem_parser_boringssl.cc
+++ b/cc/subtle/pem_parser_boringssl.cc
@@ -52,6 +52,104 @@
   return util::OkStatus();
 }
 
+// Converts the public portion of a given SubtleUtilBoringSSL::EcKey,
+// `subtle_ec_key`, into an OpenSSL EC key, `openssl_ec_key`.
+util::Status ConvertSubtleEcKeyToOpenSslEcPublicKey(
+    const SubtleUtilBoringSSL::EcKey& subtle_ec_key, EC_KEY* openssl_ec_key) {
+  if (openssl_ec_key == nullptr) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "`openssl_ec_key` arg cannot be NULL");
+  }
+
+  // Set the key's group (EC curve).
+  auto group_statusor = SubtleUtilBoringSSL::GetEcGroup(subtle_ec_key.curve);
+  if (!group_statusor.ok()) {
+    return group_statusor.status();
+  }
+  bssl::UniquePtr<EC_GROUP> group(group_statusor.ValueOrDie());
+  if (group.get() == nullptr) {
+    return util::Status(
+        util::error::INTERNAL,
+        absl::StrCat("failed to set EC group to curve ", subtle_ec_key.curve));
+  }
+  if (!EC_KEY_set_group(openssl_ec_key, group.get())) {
+    return util::Status(
+        util::error::INTERNAL,
+        absl::StrCat("failed to set key group from EC group for curve ",
+                     subtle_ec_key.curve));
+  }
+
+  // Create an EC point and initialize it from the key proto.
+  bssl::UniquePtr<EC_POINT> point(EC_POINT_new(group.get()));
+  if (!point.get()) {
+    return util::Status(util::error::INTERNAL, "failed to allocate EC_POINT");
+  }
+  bssl::UniquePtr<BIGNUM> x(BN_bin2bn(
+      reinterpret_cast<const unsigned char*>(subtle_ec_key.pub_x.data()),
+      subtle_ec_key.pub_x.length(), nullptr));
+  bssl::UniquePtr<BIGNUM> y(BN_bin2bn(
+      reinterpret_cast<const unsigned char*>(subtle_ec_key.pub_y.data()),
+      subtle_ec_key.pub_y.length(), nullptr));
+  if (!EC_POINT_set_affine_coordinates_GFp(group.get(), point.get(), x.get(),
+                                           y.get(), nullptr)) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "failed to set affine coordinates");
+  }
+  if (!EC_POINT_is_on_curve(group.get(), point.get(), nullptr)) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "failed to confirm EC point is on curve");
+  }
+
+  // Set the key's point from the EC point, created above.
+  if (!EC_KEY_set_public_key(openssl_ec_key, point.get())) {
+    return util::Status(util::error::INTERNAL, "failed to set public key");
+  }
+
+  return util::OkStatus();
+}
+
+// Converts a given SubtleUtilBoringSSL::EcKey, `subtle_ec_key`, into an OpenSSL
+// EC key, `openssl_ec_key`.
+util::Status ConvertSubtleEcKeyToOpenSslEcPrivateKey(
+    const SubtleUtilBoringSSL::EcKey& subtle_ec_key, EC_KEY* openssl_ec_key) {
+  if (openssl_ec_key == nullptr) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "`openssl_ec_key` arg cannot be NULL");
+  }
+  util::Status status =
+      ConvertSubtleEcKeyToOpenSslEcPublicKey(subtle_ec_key, openssl_ec_key);
+  if (!status.ok()) {
+    return status;
+  }
+  bssl::UniquePtr<BIGNUM> x(BN_bin2bn(
+      reinterpret_cast<const unsigned char*>(subtle_ec_key.priv.data()),
+      subtle_ec_key.priv.size(), nullptr));
+  if (!EC_KEY_set_private_key(openssl_ec_key, x.get())) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "failed to set private key");
+  }
+  if (!EC_KEY_check_key(openssl_ec_key)) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "failed private key check");
+  }
+  return util::OkStatus();
+}
+
+// Converts an OpenSSL BIO (i.e., basic IO stream), `bio`, into a string.
+util::StatusOr<std::string> ConvertBioToString(BIO* bio) {
+  BUF_MEM* mem = nullptr;
+  BIO_get_mem_ptr(bio, &mem);
+  std::string pem_material;
+  if (mem->data && mem->length) {
+    pem_material.assign(mem->data, mem->length);
+  }
+  if (pem_material.empty()) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "failed to retrieve key material from BIO");
+  }
+  return pem_material;
+}
+
 }  // namespace
 
 // static.
@@ -131,7 +229,8 @@
       absl::make_unique<SubtleUtilBoringSSL::RsaPrivateKey>();
   auto n_str = SubtleUtilBoringSSL::bn2str(n_bn, BN_num_bytes(n_bn));
   auto e_str = SubtleUtilBoringSSL::bn2str(e_bn, BN_num_bytes(e_bn));
-  auto d_str = SubtleUtilBoringSSL::bn2str(d_bn, BN_num_bytes(d_bn));
+  auto d_str =
+      SubtleUtilBoringSSL::BignumToSecretData(d_bn, BN_num_bytes(d_bn));
   if (!n_str.ok()) return n_str.status();
   if (!e_str.ok()) return e_str.status();
   if (!d_str.ok()) return d_str.status();
@@ -142,8 +241,10 @@
   // Save factors.
   const BIGNUM *p_bn, *q_bn;
   RSA_get0_factors(bssl_rsa_key, &p_bn, &q_bn);
-  auto p_str = SubtleUtilBoringSSL::bn2str(p_bn, BN_num_bytes(p_bn));
-  auto q_str = SubtleUtilBoringSSL::bn2str(q_bn, BN_num_bytes(q_bn));
+  auto p_str =
+      SubtleUtilBoringSSL::BignumToSecretData(p_bn, BN_num_bytes(p_bn));
+  auto q_str =
+      SubtleUtilBoringSSL::BignumToSecretData(q_bn, BN_num_bytes(q_bn));
   if (!p_str.ok()) return p_str.status();
   if (!q_str.ok()) return q_str.status();
   rsa_private_key->p = std::move(p_str.ValueOrDie());
@@ -152,9 +253,12 @@
   // Save CRT parameters.
   const BIGNUM *dp_bn, *dq_bn, *crt_bn;
   RSA_get0_crt_params(bssl_rsa_key, &dp_bn, &dq_bn, &crt_bn);
-  auto dp_str = SubtleUtilBoringSSL::bn2str(dp_bn, BN_num_bytes(dp_bn));
-  auto dq_str = SubtleUtilBoringSSL::bn2str(dq_bn, BN_num_bytes(dq_bn));
-  auto crt_str = SubtleUtilBoringSSL::bn2str(crt_bn, BN_num_bytes(crt_bn));
+  auto dp_str =
+      SubtleUtilBoringSSL::BignumToSecretData(dp_bn, BN_num_bytes(dp_bn));
+  auto dq_str =
+      SubtleUtilBoringSSL::BignumToSecretData(dq_bn, BN_num_bytes(dq_bn));
+  auto crt_str =
+      SubtleUtilBoringSSL::BignumToSecretData(crt_bn, BN_num_bytes(crt_bn));
   if (!dp_str.ok()) return dp_str.status();
   if (!dq_str.ok()) return dq_str.status();
   if (!crt_str.ok()) return crt_str.status();
@@ -177,6 +281,40 @@
                       "PEM EC Private Key parsing is unimplemented");
 }
 
+util::StatusOr<std::string> PemParser::WriteEcPublicKey(
+    const SubtleUtilBoringSSL::EcKey& ec_key) {
+  bssl::UniquePtr<EC_KEY> openssl_ec_key(EC_KEY_new());
+  util::Status status =
+      ConvertSubtleEcKeyToOpenSslEcPublicKey(ec_key, openssl_ec_key.get());
+  if (!status.ok()) {
+    return status;
+  }
+  bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
+  if (!PEM_write_bio_EC_PUBKEY(bio.get(), openssl_ec_key.get())) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "failed to write openssl EC key to write bio");
+  }
+  return ConvertBioToString(bio.get());
+}
+
+util::StatusOr<std::string> PemParser::WriteEcPrivateKey(
+    const SubtleUtilBoringSSL::EcKey& ec_key) {
+  bssl::UniquePtr<EC_KEY> openssl_ec_key(EC_KEY_new());
+  util::Status status =
+      ConvertSubtleEcKeyToOpenSslEcPrivateKey(ec_key, openssl_ec_key.get());
+  if (!status.ok()) {
+    return status;
+  }
+  bssl::UniquePtr<BIO> bio(BIO_new(BIO_s_mem()));
+  if (!PEM_write_bio_ECPrivateKey(bio.get(), openssl_ec_key.get(),
+                                  /*enc=*/nullptr, /*kstr=*/nullptr, /*klen=*/0,
+                                  /*cb=*/nullptr, /*u=*/nullptr)) {
+    return util::Status(util::error::INVALID_ARGUMENT,
+                        "failed to write openssl EC key to write bio");
+  }
+  return ConvertBioToString(bio.get());
+}
+
 }  // namespace subtle
 }  // namespace tink
 }  // namespace crypto
diff --git a/cc/subtle/pem_parser_boringssl.h b/cc/subtle/pem_parser_boringssl.h
index b205bee..61914ea 100644
--- a/cc/subtle/pem_parser_boringssl.h
+++ b/cc/subtle/pem_parser_boringssl.h
@@ -47,6 +47,16 @@
   // SubtleUtilBoringSSL::EcKey.
   static util::StatusOr<std::unique_ptr<SubtleUtilBoringSSL::EcKey>>
   ParseEcPrivateKey(absl::string_view pem_serialized_key);
+
+  // Writes a given SubtleUtilBoringSSL::EcKey `ec_key` into a PEM serialized
+  // EC public key.
+  static util::StatusOr<std::string> WriteEcPublicKey(
+      const SubtleUtilBoringSSL::EcKey& ec_key);
+
+  // Writes a given SubtleUtilBoringSSL::EcKey `ec_key` into a PEM serialized
+  // EC private key.
+  static util::StatusOr<std::string> WriteEcPrivateKey(
+      const SubtleUtilBoringSSL::EcKey& ec_key);
 };
 
 }  // namespace subtle
diff --git a/cc/subtle/pem_parser_boringssl_test.cc b/cc/subtle/pem_parser_boringssl_test.cc
index 2e7d3d7..8ee9522 100644
--- a/cc/subtle/pem_parser_boringssl_test.cc
+++ b/cc/subtle/pem_parser_boringssl_test.cc
@@ -20,6 +20,8 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/strings/escaping.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/string_view.h"
 #include "openssl/base.h"
 #include "openssl/bio.h"
@@ -28,6 +30,7 @@
 #include "openssl/pem.h"
 #include "openssl/rsa.h"
 #include "tink/subtle/subtle_util_boringssl.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "tink/util/test_matchers.h"
@@ -38,6 +41,177 @@
 namespace subtle {
 namespace {
 
+using ::crypto::tink::test::StatusIs;
+
+// Test vectors for ECDSA were generated using the `openssl` command.
+//
+// 1. Generate private PEM file. In the command below, the following values were
+// used for the -name flag: {prime256v1, secp384r1, secp521r1}.
+//
+// $ openssl ecparam -genkey -name prime256v1 -noout -out ec-key-pair.pem
+//
+// 2. Generate public PEM file from private PEM file.
+//
+// $ openssl ec -in ec-key-pair.pem -pubout -out pub.pem
+//
+// 3. Print public X, Y and private key components. The public component is
+// obtained by removing the leading "04" character (which indicates that the key
+// is not compressed) and splitting the remaning bytes in two. The first half is
+// X and the 2nd half is Y.
+//
+// $ openssl ec -in ec-key-pair.pem -text -param_enc explicit -noout
+struct EcKeyTestVector {
+  // EC format
+  subtle::EllipticCurveType curve;
+  std::string pub_x_hex_str;
+  std::string pub_y_hex_str;
+  std::string priv_hex_str;
+
+  // PEM format
+  std::string pub_pem;
+  std::string priv_pem;
+};
+
+static const auto *kEcKeyTestVectors = new std::vector<EcKeyTestVector>({
+    // NIST P256
+    {
+        /*.curve=*/subtle::NIST_P256,
+        /*.pub_x_hex_str=*/
+        "1455cfd594d44df125f1ff643636740c6cc59972091fee6fa9b8d3897d59b0e0",
+        /*.pub_y_hex_str=*/
+        "d0b655238d8c0cebbfde77b1fda62ad19ccc6bf25a4ebf5637d3597983094363",
+        /*.priv_hex_str=*/
+        "8485FB768E109D14BE1E219D4D806523308E0E401DB1DE95DC938E8903C49B2C",
+        /*.pub_pem=*/R"(-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFFXP1ZTUTfEl8f9kNjZ0DGzFmXIJ
+H+5vqbjTiX1ZsODQtlUjjYwM67/ed7H9pirRnMxr8lpOv1Y301l5gwlDYw==
+-----END PUBLIC KEY-----)",
+        /*.priv_pem=*/R"(-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIISF+3aOEJ0Uvh4hnU2AZSMwjg5AHbHeldyTjokDxJssoAoGCCqGSM49
+AwEHoUQDQgAEFFXP1ZTUTfEl8f9kNjZ0DGzFmXIJH+5vqbjTiX1ZsODQtlUjjYwM
+67/ed7H9pirRnMxr8lpOv1Y301l5gwlDYw==
+-----END EC PRIVATE KEY-----)",
+    },
+    {
+        /*.curve=*/subtle::NIST_P256,
+        /*.pub_x_hex_str=*/
+        "ee21893b340260360f1ae3d26bf0a066eadc8c63690b2f1de308220800d9d1ab",
+        /*.pub_y_hex_str=*/
+        "d334a5917d2be49475af2454feea41d4418ea99eec791d1a0cc1c2890f8b33ee",
+        /*.priv_hex_str=*/
+        "cac853f79a95c7d8697d0469ccda4faf940d80d1e0c81ffa0e6082ed9a85654b",
+        /*.pub_pem=*/R"(-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7iGJOzQCYDYPGuPSa/CgZurcjGNp
+Cy8d4wgiCADZ0avTNKWRfSvklHWvJFT+6kHUQY6pnux5HRoMwcKJD4sz7g==
+-----END PUBLIC KEY-----)",
+        /*.priv_pem=*/R"(-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIMrIU/ealcfYaX0EaczaT6+UDYDR4Mgf+g5ggu2ahWVLoAoGCCqGSM49
+AwEHoUQDQgAE7iGJOzQCYDYPGuPSa/CgZurcjGNpCy8d4wgiCADZ0avTNKWRfSvk
+lHWvJFT+6kHUQY6pnux5HRoMwcKJD4sz7g==
+-----END EC PRIVATE KEY-----)",
+    },
+
+    // NIST P384
+    {
+        /*.curve=*/subtle::NIST_P384,
+        /*.pub_x_hex_str=*/
+        "49b1a78537281c81984e00092f04c22c610cac2aba7a3de992bf6ad22305d2d5450187"
+        "57ed823c643334e18d95b2e642",
+        /*.pub_y_hex_str=*/
+        "d2a851445c5da0bf0d543eaad5ff98634483c549d96045243121ed6d5c9ba64dab656a"
+        "6d25e018b01c4d3ab3f1738989",
+        /*.priv_hex_str=*/
+        "0254cd5840eec13b0d68ba08fdbc147c22906046ecb2fca2625294be74dea29aa370fd"
+        "830985d278099644ecf89167cd",
+        /*.pub_pem=*/R"(-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAESbGnhTcoHIGYTgAJLwTCLGEMrCq6ej3p
+kr9q0iMF0tVFAYdX7YI8ZDM04Y2VsuZC0qhRRFxdoL8NVD6q1f+YY0SDxUnZYEUk
+MSHtbVybpk2rZWptJeAYsBxNOrPxc4mJ
+-----END PUBLIC KEY-----)",
+        /*.priv_pem=*/R"(-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDACVM1YQO7BOw1ougj9vBR8IpBgRuyy/KJiUpS+dN6imqNw/YMJhdJ4
+CZZE7PiRZ82gBwYFK4EEACKhZANiAARJsaeFNygcgZhOAAkvBMIsYQysKrp6PemS
+v2rSIwXS1UUBh1ftgjxkMzThjZWy5kLSqFFEXF2gvw1UPqrV/5hjRIPFSdlgRSQx
+Ie1tXJumTatlam0l4BiwHE06s/FziYk=
+-----END EC PRIVATE KEY-----)",
+    },
+    {
+        /*.curve=*/subtle::NIST_P384,
+        /*.pub_x_hex_str=*/
+        "82de2530a8d589149c8a60fdd529ed7a465db62d7412771a7ec40a69be139226b60906"
+        "cc784007d8e28a79dca528e66c",
+        /*.pub_y_hex_str=*/
+        "c41f7532b8325aad3f1dddebddb702ebe70259bb5730e6bc4a75baec0d85c52d0d00c8"
+        "e372d1da0d1ca10136e4cfd262",
+        /*.priv_hex_str=*/
+        "a6a8415b526418966758cfda45c19b4fe0ac4cf06d301b195ffea231d0eda67a54fb7c"
+        "bc12470296e29e86359de53aee",
+        /*.pub_pem=*/R"(-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEgt4lMKjViRScimD91SntekZdti10Enca
+fsQKab4Tkia2CQbMeEAH2OKKedylKOZsxB91MrgyWq0/Hd3r3bcC6+cCWbtXMOa8
+SnW67A2FxS0NAMjjctHaDRyhATbkz9Ji
+-----END PUBLIC KEY-----)",
+        /*.priv_pem=*/R"(-----BEGIN EC PRIVATE KEY-----
+MIGkAgEBBDCmqEFbUmQYlmdYz9pFwZtP4KxM8G0wGxlf/qIx0O2melT7fLwSRwKW
+4p6GNZ3lOu6gBwYFK4EEACKhZANiAASC3iUwqNWJFJyKYP3VKe16Rl22LXQSdxp+
+xAppvhOSJrYJBsx4QAfY4op53KUo5mzEH3UyuDJarT8d3evdtwLr5wJZu1cw5rxK
+dbrsDYXFLQ0AyONy0doNHKEBNuTP0mI=
+-----END EC PRIVATE KEY-----)",
+    },
+
+    // NIST P521
+    {
+        /*.curve=*/subtle::NIST_P521,
+        /*.pub_x_hex_str=*/
+        "01d09ee2f33ce601d8594b09e668e128a7708ce752ef589d1a2c405523db0b68a0cb58"
+        "60359b12c5371fc462f4142339ca7ff2550833f0a64887951ddb64e7d139d5",
+        /*.pub_y_hex_str=*/
+        "01b45ce12804afcf17fbd60728d362d4787b750d561e52144fd517807ddaa2b396bed9"
+        "98227a5696d9c997a1cf0b6f1a3724ce25c7396dc2ea62c4bdf467061916e3",
+        /*.priv_hex_str=*/
+        "01f1913a921271c06686482a51dbf2c853aefbcc62b2b23d473a4c818d570e55566742"
+        "edd9f05d0532f73d40c11d3d31c3734e4470cc0491ad911a209f1e88dcd712",
+        /*.pub_pem=*/R"(-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQB0J7i8zzmAdhZSwnmaOEop3CM51Lv
+WJ0aLEBVI9sLaKDLWGA1mxLFNx/EYvQUIznKf/JVCDPwpkiHlR3bZOfROdUBtFzh
+KASvzxf71gco02LUeHt1DVYeUhRP1ReAfdqis5a+2ZgielaW2cmXoc8Lbxo3JM4l
+xzltwupixL30ZwYZFuM=
+-----END PUBLIC KEY-----)",
+        /*.priv_pem=*/R"(-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIB8ZE6khJxwGaGSCpR2/LIU677zGKysj1HOkyBjVcOVVZnQu3Z8F0F
+Mvc9QMEdPTHDc05EcMwEka2RGiCfHojc1xKgBwYFK4EEACOhgYkDgYYABAHQnuLz
+POYB2FlLCeZo4SincIznUu9YnRosQFUj2wtooMtYYDWbEsU3H8Ri9BQjOcp/8lUI
+M/CmSIeVHdtk59E51QG0XOEoBK/PF/vWByjTYtR4e3UNVh5SFE/VF4B92qKzlr7Z
+mCJ6VpbZyZehzwtvGjckziXHOW3C6mLEvfRnBhkW4w==
+-----END EC PRIVATE KEY-----)",
+    },
+    {
+        /*.curve=*/subtle::NIST_P521,
+        /*.pub_x_hex_str=*/
+        "0108803f92f8449fdfca02c8c2b49643f407f63dda728ad38e3598b887b5831ab063d9"
+        "60c5fd321ee597f4273fc0596015ce406515a2ab24a7c96a44802d74c3ac7b",
+        /*.pub_y_hex_str=*/
+        "01f216dc0f590b920d9c026e0aedc2b1cfe85d4f2d607db632395c7f64c05328593633"
+        "635b6ad8bf51d2ee70c88000e96fd340601211c1d1eb0b32773806506b47b0",
+        /*.priv_hex_str=*/
+        "0010a207e650cf531c98c0c6d1cfdb88a5ee57f02734cbab93b8ae30d9dac0845d1761"
+        "9be33f9aeaeab35401e63a149a87ae45b45bf2fea125d96c5d418d96bcda85",
+        /*.pub_pem=*/R"(-----BEGIN PUBLIC KEY-----
+MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBCIA/kvhEn9/KAsjCtJZD9Af2Pdpy
+itOONZi4h7WDGrBj2WDF/TIe5Zf0Jz/AWWAVzkBlFaKrJKfJakSALXTDrHsB8hbc
+D1kLkg2cAm4K7cKxz+hdTy1gfbYyOVx/ZMBTKFk2M2Nbati/UdLucMiAAOlv00Bg
+EhHB0esLMnc4BlBrR7A=
+-----END PUBLIC KEY-----)",
+        /*.priv_pem=*/R"(-----BEGIN EC PRIVATE KEY-----
+MIHcAgEBBEIAEKIH5lDPUxyYwMbRz9uIpe5X8Cc0y6uTuK4w2drAhF0XYZvjP5rq
+6rNUAeY6FJqHrkW0W/L+oSXZbF1BjZa82oWgBwYFK4EEACOhgYkDgYYABAEIgD+S
++ESf38oCyMK0lkP0B/Y92nKK0441mLiHtYMasGPZYMX9Mh7ll/QnP8BZYBXOQGUV
+oqskp8lqRIAtdMOsewHyFtwPWQuSDZwCbgrtwrHP6F1PLWB9tjI5XH9kwFMoWTYz
+Y1tq2L9R0u5wyIAA6W/TQGASEcHR6wsydzgGUGtHsA==
+-----END EC PRIVATE KEY-----)",
+    },
+});
+
 class PemParserTest : public ::testing::Test {
  public:
   PemParserTest() : rsa_(RSA_new()) {}
@@ -129,26 +303,26 @@
             SubtleUtilBoringSSL::bn2str(e_bn, BN_num_bytes(e_bn)).ValueOrDie());
   EXPECT_EQ(key->n,
             SubtleUtilBoringSSL::bn2str(n_bn, BN_num_bytes(n_bn)).ValueOrDie());
-  EXPECT_EQ(key->d,
+  EXPECT_EQ(util::SecretDataAsStringView(key->d),
             SubtleUtilBoringSSL::bn2str(d_bn, BN_num_bytes(d_bn)).ValueOrDie());
   // Verify private key factors.
   const BIGNUM *p_bn, *q_bn;
   RSA_get0_factors(rsa_.get(), &p_bn, &q_bn);
-  EXPECT_EQ(key->p,
+  EXPECT_EQ(util::SecretDataAsStringView(key->p),
             SubtleUtilBoringSSL::bn2str(p_bn, BN_num_bytes(p_bn)).ValueOrDie());
-  EXPECT_EQ(key->q,
+  EXPECT_EQ(util::SecretDataAsStringView(key->q),
             SubtleUtilBoringSSL::bn2str(q_bn, BN_num_bytes(q_bn)).ValueOrDie());
   // Verify CRT parameters.
   const BIGNUM *dp_bn, *dq_bn, *crt_bn;
   RSA_get0_crt_params(rsa_.get(), &dp_bn, &dq_bn, &crt_bn);
   EXPECT_EQ(
-      key->dp,
+      util::SecretDataAsStringView(key->dp),
       SubtleUtilBoringSSL::bn2str(dp_bn, BN_num_bytes(dp_bn)).ValueOrDie());
   EXPECT_EQ(
-      key->dq,
+      util::SecretDataAsStringView(key->dq),
       SubtleUtilBoringSSL::bn2str(dq_bn, BN_num_bytes(dq_bn)).ValueOrDie());
   EXPECT_EQ(
-      key->crt,
+      util::SecretDataAsStringView(key->crt),
       SubtleUtilBoringSSL::bn2str(crt_bn, BN_num_bytes(crt_bn)).ValueOrDie());
 }
 
@@ -168,6 +342,73 @@
            .ok());
 }
 
+TEST_F(PemParserTest, WriteEcPublicKeySucceeds) {
+  for (const auto& test_vector : *kEcKeyTestVectors) {
+    // Load an EcKey with the test vector.
+    SubtleUtilBoringSSL::EcKey ec_key;
+    ec_key.curve = test_vector.curve;
+    ec_key.pub_x = absl::HexStringToBytes(test_vector.pub_x_hex_str);
+    ec_key.pub_y = absl::HexStringToBytes(test_vector.pub_y_hex_str);
+    ec_key.priv = util::SecretDataFromStringView(
+        absl::HexStringToBytes(test_vector.priv_hex_str));
+
+    // Check that converting the public key with WriteEcPublicKey() succeeds.
+    auto pem_material_statusor = PemParser::WriteEcPublicKey(ec_key);
+    ASSERT_TRUE(pem_material_statusor.ok()) << pem_material_statusor.status();
+    std::string pem_material = pem_material_statusor.ValueOrDie();
+    EXPECT_TRUE(absl::StripAsciiWhitespace(pem_material) ==
+                absl::StripAsciiWhitespace(test_vector.pub_pem));
+  }
+}
+
+TEST_F(PemParserTest, WriteEcPrivateKeySucceeds) {
+  for (const auto& test_vector : *kEcKeyTestVectors) {
+    // Load an EcKey with the test vector.
+    SubtleUtilBoringSSL::EcKey ec_key;
+    ec_key.curve = test_vector.curve;
+    ec_key.pub_x = absl::HexStringToBytes(test_vector.pub_x_hex_str);
+    ec_key.pub_y = absl::HexStringToBytes(test_vector.pub_y_hex_str);
+    ec_key.priv = util::SecretDataFromStringView(
+        absl::HexStringToBytes(test_vector.priv_hex_str));
+
+    // Check that converting the private key with WriteEcPrivateKey() succeeds.
+    auto pem_material_statusor = PemParser::WriteEcPrivateKey(ec_key);
+    ASSERT_TRUE(pem_material_statusor.ok()) << pem_material_statusor.status();
+    std::string pem_material = pem_material_statusor.ValueOrDie();
+    EXPECT_TRUE(absl::StripAsciiWhitespace(pem_material) ==
+                absl::StripAsciiWhitespace(test_vector.priv_pem));
+  }
+}
+
+TEST_F(PemParserTest, WriteEcPublicKeyWithBadXFails) {
+  auto ec_key_statusor = SubtleUtilBoringSSL::GetNewEcKey(subtle::NIST_P256);
+  ASSERT_TRUE(ec_key_statusor.ok()) << ec_key_statusor.status();
+  SubtleUtilBoringSSL::EcKey ec_key = ec_key_statusor.ValueOrDie();
+  Corrupt(&ec_key.pub_x);
+  EXPECT_THAT(PemParser::WriteEcPublicKey(ec_key).status(),
+              StatusIs(util::error::INVALID_ARGUMENT));
+}
+
+TEST_F(PemParserTest, WriteEcPublicKeyWithBadYFails) {
+  auto ec_key_statusor = SubtleUtilBoringSSL::GetNewEcKey(subtle::NIST_P256);
+  ASSERT_TRUE(ec_key_statusor.ok()) << ec_key_statusor.status();
+  SubtleUtilBoringSSL::EcKey ec_key = ec_key_statusor.ValueOrDie();
+  Corrupt(&ec_key.pub_y);
+  EXPECT_THAT(PemParser::WriteEcPublicKey(ec_key).status(),
+              StatusIs(util::error::INVALID_ARGUMENT));
+}
+
+TEST_F(PemParserTest, WriteEcPrivateKeyWithBadPrivFails) {
+  auto ec_key_statusor = SubtleUtilBoringSSL::GetNewEcKey(subtle::NIST_P256);
+  ASSERT_TRUE(ec_key_statusor.ok()) << ec_key_statusor.status();
+  SubtleUtilBoringSSL::EcKey ec_key = ec_key_statusor.ValueOrDie();
+  std::string priv = std::string(util::SecretDataAsStringView(ec_key.priv));
+  Corrupt(&priv);
+  ec_key.priv = util::SecretDataFromStringView(priv);
+  EXPECT_THAT(PemParser::WriteEcPrivateKey(ec_key).status(),
+              StatusIs(util::error::INVALID_ARGUMENT));
+}
+
 }  // namespace
 }  // namespace subtle
 }  // namespace tink
diff --git a/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc
index c10ea1b..2b22438 100644
--- a/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc
+++ b/cc/subtle/rsa_ssa_pkcs1_sign_boringssl.cc
@@ -18,6 +18,7 @@
 
 #include <vector>
 
+#include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "openssl/base.h"
 #include "openssl/digest.h"
@@ -73,8 +74,8 @@
                                      SubtleUtilBoringSSL::GetErrors()));
   }
 
-  return std::unique_ptr<PublicKeySign>(
-      new RsaSsaPkcs1SignBoringSsl(std::move(rsa), sig_hash.ValueOrDie()));
+  return {absl::WrapUnique(
+      new RsaSsaPkcs1SignBoringSsl(std::move(rsa), sig_hash.ValueOrDie()))};
 }
 
 util::StatusOr<std::string> RsaSsaPkcs1SignBoringSsl::Sign(
diff --git a/cc/subtle/rsa_ssa_pss_sign_boringssl.cc b/cc/subtle/rsa_ssa_pss_sign_boringssl.cc
index 0253083..13045f2 100644
--- a/cc/subtle/rsa_ssa_pss_sign_boringssl.cc
+++ b/cc/subtle/rsa_ssa_pss_sign_boringssl.cc
@@ -18,6 +18,7 @@
 
 #include <vector>
 
+#include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "openssl/base.h"
 #include "openssl/evp.h"
@@ -72,9 +73,9 @@
                                      SubtleUtilBoringSSL::GetErrors()));
   }
 
-  return std::unique_ptr<PublicKeySign>(
+  return {absl::WrapUnique(
       new RsaSsaPssSignBoringSsl(std::move(rsa), sig_hash.ValueOrDie(),
-                                 mgf1_hash.ValueOrDie(), params.salt_length));
+                                 mgf1_hash.ValueOrDie(), params.salt_length))};
 }
 
 RsaSsaPssSignBoringSsl::RsaSsaPssSignBoringSsl(bssl::UniquePtr<RSA> private_key,
diff --git a/cc/subtle/subtle_util_boringssl.cc b/cc/subtle/subtle_util_boringssl.cc
index 157a73d..fb7c97d 100644
--- a/cc/subtle/subtle_util_boringssl.cc
+++ b/cc/subtle/subtle_util_boringssl.cc
@@ -16,6 +16,9 @@
 
 #include "tink/subtle/subtle_util_boringssl.h"
 
+#include <algorithm>
+#include <iterator>
+
 #include "absl/memory/memory.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/substitute.h"
@@ -28,8 +31,10 @@
 #include "openssl/err.h"
 #include "openssl/mem.h"
 #include "openssl/rsa.h"
+#include "tink/config/tink_fips.h"
 #include "tink/subtle/common_enums.h"
 #include "tink/util/errors.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 
 namespace crypto {
@@ -165,11 +170,12 @@
     return pub_y_str.status();
   }
   ec_key.pub_y = pub_y_str.ValueOrDie();
-  auto priv_key_str = bn2str(priv_key, ScalarSizeInBytes(group.get()));
-  if (!priv_key_str.ok()) {
-    return priv_key_str.status();
+  auto priv_key_or =
+      BignumToSecretData(priv_key, ScalarSizeInBytes(group.get()));
+  if (!priv_key_or.ok()) {
+    return priv_key_or.status();
   }
-  ec_key.priv = priv_key_str.ValueOrDie();
+  ec_key.priv = priv_key_or.ValueOrDie();
   return ec_key;
 }
 
@@ -191,9 +197,8 @@
   ec_key.pub_x =
       std::string(reinterpret_cast<const char *>(x25519_key->public_value),
                   X25519_PUBLIC_VALUE_LEN);
-  ec_key.priv =
-      std::string(reinterpret_cast<const char *>(x25519_key->private_key),
-                  X25519_PRIVATE_KEY_LEN);
+  ec_key.priv = util::SecretData(std::begin(x25519_key->private_key),
+                                 std::end(x25519_key->private_key));
   return ec_key;
 }
 
@@ -211,10 +216,10 @@
                         "Invalid X25519 key. pub_y is unexpectedly set.");
   }
   // Curve25519 public key is x, not (x,y).
-  ec_key.pub_x.copy(reinterpret_cast<char *>(x25519_key->public_value),
-                    X25519_PUBLIC_VALUE_LEN);
-  ec_key.priv.copy(reinterpret_cast<char *>(x25519_key->private_key),
-                   X25519_PRIVATE_KEY_LEN);
+  std::copy_n(ec_key.pub_x.begin(), X25519_PUBLIC_VALUE_LEN,
+              std::begin(x25519_key->public_value));
+  std::copy_n(ec_key.priv.begin(), X25519_PRIVATE_KEY_LEN,
+              std::begin(x25519_key->private_key));
   return std::move(x25519_key);
 }
 
@@ -506,11 +511,22 @@
 // static
 util::Status SubtleUtilBoringSSL::ValidateRsaModulusSize(size_t modulus_size) {
   if (modulus_size < 2048) {
-    return ToStatusF(
+    return util::Status(
         util::error::INVALID_ARGUMENT,
-        "Modulus size is %u; only modulus size >= 2048-bit is supported",
-        modulus_size);
+        absl::StrCat("Modulus size is ", modulus_size,
+                     " only modulus size >= 2048-bit is supported"));
   }
+
+  // In FIPS only mode we check here if the modulus is 3072, as this is the
+  // only size which is covered by the FIPS validation and supported by Tink.
+  // See
+  // https://csrc.nist.gov/projects/cryptographic-module-validation-program/certificate/3318
+  if (kUseOnlyFips && (modulus_size != 3072)) {
+    return util::Status(util::error::INTERNAL,
+                        absl::StrCat("Modulus size is ", modulus_size,
+                                     " only modulus size 3072 is supported "));
+  }
+
   return util::Status::OK;
 }
 
@@ -560,7 +576,7 @@
   // Save exponents.
   auto n_str = bn2str(n_bn, BN_num_bytes(n_bn));
   auto e_str = bn2str(e_bn, BN_num_bytes(e_bn));
-  auto d_str = bn2str(d_bn, BN_num_bytes(d_bn));
+  auto d_str = BignumToSecretData(d_bn, BN_num_bytes(d_bn));
   if (!n_str.ok()) return n_str.status();
   if (!e_str.ok()) return e_str.status();
   if (!d_str.ok()) return d_str.status();
@@ -574,8 +590,8 @@
   // Save factors.
   const BIGNUM *p_bn, *q_bn;
   RSA_get0_factors(rsa.get(), &p_bn, &q_bn);
-  auto p_str = bn2str(p_bn, BN_num_bytes(p_bn));
-  auto q_str = bn2str(q_bn, BN_num_bytes(q_bn));
+  auto p_str = BignumToSecretData(p_bn, BN_num_bytes(p_bn));
+  auto q_str = BignumToSecretData(q_bn, BN_num_bytes(q_bn));
   if (!p_str.ok()) return p_str.status();
   if (!q_str.ok()) return q_str.status();
   private_key->p = std::move(p_str.ValueOrDie());
@@ -584,9 +600,9 @@
   // Save CRT parameters.
   const BIGNUM *dp_bn, *dq_bn, *crt_bn;
   RSA_get0_crt_params(rsa.get(), &dp_bn, &dq_bn, &crt_bn);
-  auto dp_str = bn2str(dp_bn, BN_num_bytes(dp_bn));
-  auto dq_str = bn2str(dq_bn, BN_num_bytes(dq_bn));
-  auto crt_str = bn2str(crt_bn, BN_num_bytes(crt_bn));
+  auto dp_str = BignumToSecretData(dp_bn, BN_num_bytes(dp_bn));
+  auto dq_str = BignumToSecretData(dq_bn, BN_num_bytes(dq_bn));
+  auto crt_str = BignumToSecretData(crt_bn, BN_num_bytes(crt_bn));
   if (!dp_str.ok()) return dp_str.status();
   if (!dq_str.ok()) return dq_str.status();
   if (!crt_str.ok()) return crt_str.status();
@@ -602,7 +618,7 @@
     const SubtleUtilBoringSSL::RsaPrivateKey &key, RSA *rsa) {
   auto n = SubtleUtilBoringSSL::str2bn(key.n);
   auto e = SubtleUtilBoringSSL::str2bn(key.e);
-  auto d = SubtleUtilBoringSSL::str2bn(key.d);
+  auto d = SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(key.d));
   if (!n.ok()) return n.status();
   if (!e.ok()) return e.status();
   if (!d.ok()) return d.status();
@@ -622,8 +638,8 @@
 // static
 util::Status SubtleUtilBoringSSL::CopyPrimeFactors(
     const SubtleUtilBoringSSL::RsaPrivateKey &key, RSA *rsa) {
-  auto p = SubtleUtilBoringSSL::str2bn(key.p);
-  auto q = SubtleUtilBoringSSL::str2bn(key.q);
+  auto p = SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(key.p));
+  auto q = SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(key.q));
   if (!p.ok()) return p.status();
   if (!q.ok()) return q.status();
   if (RSA_set0_factors(rsa, p.ValueOrDie().get(), q.ValueOrDie().get()) != 1) {
@@ -639,9 +655,9 @@
 // static
 util::Status SubtleUtilBoringSSL::CopyCrtParams(
     const SubtleUtilBoringSSL::RsaPrivateKey &key, RSA *rsa) {
-  auto dp = SubtleUtilBoringSSL::str2bn(key.dp);
-  auto dq = SubtleUtilBoringSSL::str2bn(key.dq);
-  auto crt = SubtleUtilBoringSSL::str2bn(key.crt);
+  auto dp = SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(key.dp));
+  auto dq = SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(key.dq));
+  auto crt = SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(key.crt));
   if (!dp.ok()) return dp.status();
   if (!dq.ok()) return dq.status();
   if (!crt.ok()) return crt.status();
diff --git a/cc/subtle/subtle_util_boringssl.h b/cc/subtle/subtle_util_boringssl.h
index 4c87428..360c58e 100644
--- a/cc/subtle/subtle_util_boringssl.h
+++ b/cc/subtle/subtle_util_boringssl.h
@@ -41,7 +41,7 @@
     EllipticCurveType curve;
     std::string pub_x;  // affine coordinates in bigendian representation
     std::string pub_y;
-    std::string priv;  // big integer in bigendian representation
+    util::SecretData priv;  // big integer in bigendian representation
   };
 
   struct X25519Key {
@@ -94,22 +94,22 @@
     std::string e;
     // Private exponent.
     // Unsigned big integer in bigendian representation.
-    std::string d;
+    util::SecretData d;
 
     // The prime factor p of n.
     // Unsigned big integer in bigendian representation.
-    std::string p;
+    util::SecretData p;
     // The prime factor q of n.
     // Unsigned big integer in bigendian representation.
-    std::string q;
+    util::SecretData q;
     // d mod (p - 1).
-    std::string dp;
+    util::SecretData dp;
     // d mod (q - 1).
     // Unsigned big integer in bigendian representation.
-    std::string dq;
+    util::SecretData dq;
     // Chinese Remainder Theorem coefficient q^(-1) mod p.
     // Unsigned big integer in bigendian representation.
-    std::string crt;
+    util::SecretData crt;
   };
 
   // Returns BoringSSL's BIGNUM constructed from bigendian string
diff --git a/cc/subtle/subtle_util_boringssl_test.cc b/cc/subtle/subtle_util_boringssl_test.cc
index 142502e..e439238 100644
--- a/cc/subtle/subtle_util_boringssl_test.cc
+++ b/cc/subtle/subtle_util_boringssl_test.cc
@@ -33,6 +33,7 @@
 #include "tink/subtle/common_enums.h"
 #include "tink/subtle/ec_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"
@@ -327,11 +328,21 @@
                                                     &public_key),
               IsOk());
   auto n = std::move(SubtleUtilBoringSSL::str2bn(private_key.n).ValueOrDie());
-  auto d = std::move(SubtleUtilBoringSSL::str2bn(private_key.d).ValueOrDie());
-  auto p = std::move(SubtleUtilBoringSSL::str2bn(private_key.p).ValueOrDie());
-  auto q = std::move(SubtleUtilBoringSSL::str2bn(private_key.q).ValueOrDie());
-  auto dp = std::move(SubtleUtilBoringSSL::str2bn(private_key.dp).ValueOrDie());
-  auto dq = std::move(SubtleUtilBoringSSL::str2bn(private_key.dq).ValueOrDie());
+  auto d = std::move(
+      SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(private_key.d))
+          .ValueOrDie());
+  auto p = std::move(
+      SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(private_key.p))
+          .ValueOrDie());
+  auto q = std::move(
+      SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(private_key.q))
+          .ValueOrDie());
+  auto dp = std::move(
+      SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(private_key.dp))
+          .ValueOrDie());
+  auto dq = std::move(
+      SubtleUtilBoringSSL::str2bn(util::SecretDataAsStringView(private_key.dq))
+          .ValueOrDie());
   bssl::UniquePtr<BN_CTX> ctx(BN_CTX_new());
 
   // Check n = p * q.
@@ -440,7 +451,7 @@
   EXPECT_EQ(ec_key.curve, EllipticCurveType::CURVE25519);
   EXPECT_EQ(ec_key.pub_x.length(), X25519_PUBLIC_VALUE_LEN);
   EXPECT_TRUE(ec_key.pub_y.empty());
-  EXPECT_EQ(ec_key.priv.length(), X25519_PRIVATE_KEY_LEN);
+  EXPECT_EQ(ec_key.priv.size(), X25519_PRIVATE_KEY_LEN);
 }
 
 TEST(CreateNewX25519KeyTest, GeneratesDifferentKeysEveryTime) {
diff --git a/cc/subtle/xchacha20_poly1305_boringssl.cc b/cc/subtle/xchacha20_poly1305_boringssl.cc
index 28c892d..ac7f11f 100644
--- a/cc/subtle/xchacha20_poly1305_boringssl.cc
+++ b/cc/subtle/xchacha20_poly1305_boringssl.cc
@@ -23,6 +23,7 @@
 #include "openssl/err.h"
 #include "openssl/evp.h"
 #include "tink/aead.h"
+#include "tink/config/tink_fips.h"
 #include "tink/subtle/random.h"
 #include "tink/subtle/subtle_util.h"
 #include "tink/subtle/subtle_util_boringssl.h"
@@ -42,6 +43,9 @@
 
 util::StatusOr<std::unique_ptr<Aead>> XChacha20Poly1305BoringSsl::New(
     util::SecretData key) {
+  auto status = CheckFipsCompatibility<XChacha20Poly1305BoringSsl>();
+  if (!status.ok()) return status;
+
   if (!IsValidKeySize(key.size())) {
     return util::Status(util::error::INVALID_ARGUMENT, "Invalid key size");
   }
diff --git a/cc/subtle/xchacha20_poly1305_boringssl.h b/cc/subtle/xchacha20_poly1305_boringssl.h
index c603633..ab6272c 100644
--- a/cc/subtle/xchacha20_poly1305_boringssl.h
+++ b/cc/subtle/xchacha20_poly1305_boringssl.h
@@ -23,6 +23,7 @@
 #include "absl/strings/string_view.h"
 #include "openssl/base.h"
 #include "tink/aead.h"
+#include "tink/config/tink_fips.h"
 #include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
@@ -48,6 +49,9 @@
       absl::string_view ciphertext,
       absl::string_view additional_data) const override;
 
+  static constexpr crypto::tink::FipsCompatibility kFipsStatus =
+      crypto::tink::FipsCompatibility::kNotFips;
+
  private:
   // The following constants are in bytes.
   static constexpr int kNonceSize = 24;
diff --git a/cc/subtle/xchacha20_poly1305_boringssl_test.cc b/cc/subtle/xchacha20_poly1305_boringssl_test.cc
index 76dcee1..5ec9341 100644
--- a/cc/subtle/xchacha20_poly1305_boringssl_test.cc
+++ b/cc/subtle/xchacha20_poly1305_boringssl_test.cc
@@ -24,6 +24,7 @@
 #include "openssl/err.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
+#include "tink/util/test_matchers.h"
 #include "tink/util/test_util.h"
 
 namespace crypto {
@@ -31,7 +32,13 @@
 namespace subtle {
 namespace {
 
-TEST(XChacha20Poly1305BoringSslTest, testBasic) {
+using ::crypto::tink::test::StatusIs;
+
+TEST(XChacha20Poly1305BoringSslTest, TestBasic) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(test::HexDecodeOrDie(
       "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
   auto res = XChacha20Poly1305BoringSsl::New(key);
@@ -48,7 +55,11 @@
   EXPECT_EQ(pt.ValueOrDie(), message);
 }
 
-TEST(XChacha20Poly1305BoringSslTest, testModification) {
+TEST(XChacha20Poly1305BoringSslTest, TestModification) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   util::SecretData key = util::SecretDataFromStringView(test::HexDecodeOrDie(
       "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
   auto cipher = std::move(XChacha20Poly1305BoringSsl::New(key).ValueOrDie());
@@ -78,6 +89,10 @@
 
 void TestDecryptWithEmptyAad(crypto::tink::Aead* cipher, absl::string_view ct,
                              absl::string_view message) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   {  // AAD is a null string_view.
     const absl::string_view aad;
     auto pt_or_status = cipher->Decrypt(ct, aad);
@@ -99,7 +114,11 @@
   }
 }
 
-TEST(XChacha20Poly1305BoringSslTest, testAadEmptyVersusNullStringView) {
+TEST(XChacha20Poly1305BoringSslTest, TestAadEmptyVersusNullStringView) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   const util::SecretData key =
       util::SecretDataFromStringView(test::HexDecodeOrDie(
           "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
@@ -128,7 +147,11 @@
   }
 }
 
-TEST(XChacha20Poly1305BoringSslTest, testMessageEmptyVersusNullStringView) {
+TEST(XChacha20Poly1305BoringSslTest, TestMessageEmptyVersusNullStringView) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   const util::SecretData key =
       util::SecretDataFromStringView(test::HexDecodeOrDie(
           "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
@@ -165,7 +188,11 @@
   }
 }
 
-TEST(XChacha20Poly1305BoringSslTest, testBothMessageAndAadEmpty) {
+TEST(XChacha20Poly1305BoringSslTest, TestBothMessageAndAadEmpty) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   const util::SecretData key =
       util::SecretDataFromStringView(test::HexDecodeOrDie(
           "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
@@ -203,7 +230,11 @@
   }
 }
 
-TEST(XChacha20Poly1305BoringSslTest, testInvalidKeySizes) {
+TEST(XChacha20Poly1305BoringSslTest, TestInvalidKeySizes) {
+  if (kUseOnlyFips) {
+    GTEST_SKIP() << "Not supported in FIPS-only mode";
+  }
+
   for (int keysize = 0; keysize < 65; keysize++) {
     if (keysize == 32) {
       continue;
@@ -214,6 +245,18 @@
   }
 }
 
+TEST(XChacha20Poly1305BoringSslTest, TestFipsOnly) {
+  if (!kUseOnlyFips) {
+    GTEST_SKIP() << "Only supported in FIPS-only mode";
+  }
+
+  util::SecretData key256 = util::SecretDataFromStringView(test::HexDecodeOrDie(
+      "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f"));
+
+  auto xchacha20_poly1305_res = subtle::XChacha20Poly1305BoringSsl::New(key256);
+  EXPECT_THAT(xchacha20_poly1305_res.status(), StatusIs(util::error::INTERNAL));
+}
+
 }  // namespace
 }  // namespace subtle
 }  // namespace tink
diff --git a/cc/third_party/aws_c_common.BUILD.bazel b/cc/third_party/aws_c_common.BUILD.bazel
new file mode 100644
index 0000000..de42eef
--- /dev/null
+++ b/cc/third_party/aws_c_common.BUILD.bazel
@@ -0,0 +1,29 @@
+licenses(["notice"])  # Apache 2.0
+
+load("@tink_base//tools:common.bzl", "template_rule")
+
+cc_library(
+    name = "aws_c_common",
+    srcs = glob(["source/*.c"]) +
+           glob(["source/posix/*.c"]),
+    hdrs = [
+        "include/aws/common/config.h"
+    ] + glob([
+        "include/**/*.h",
+        "include/aws/common/**/*.inl"
+    ]),
+    includes = ["include/"],
+    visibility = ["//visibility:public"],
+)
+
+template_rule(
+    name = "config.h",
+    src = "include/aws/common/config.h.in",
+    out = "include/aws/common/config.h",
+    substitutions = {
+        "cmakedefine AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS": "undef AWS_HAVE_GCC_OVERFLOW_MATH_EXTENSIONS",
+        "cmakedefine AWS_HAVE_GCC_INLINE_ASM": "define AWS_HAVE_GCC_INLINE_ASM",
+        "cmakedefine AWS_HAVE_MSVC_MULX": "undef AWS_HAVE_MSVC_MULX",
+        "cmakedefine AWS_HAVE_EXECINFO": "define AWS_HAVE_EXECINFO",
+    },
+)
diff --git a/cc/third_party/aws_c_event_stream.BUILD.bazel b/cc/third_party/aws_c_event_stream.BUILD.bazel
new file mode 100644
index 0000000..8f37e56
--- /dev/null
+++ b/cc/third_party/aws_c_event_stream.BUILD.bazel
@@ -0,0 +1,19 @@
+licenses(["notice"])  # Apache 2.0
+
+cc_library(
+    name = "aws_c_event_stream",
+    srcs = glob([
+        "source/*.c",
+    ]),
+    hdrs = glob([
+        "include/**/*.h"
+    ]),
+    includes = [
+        "include/",
+    ],
+    deps = [
+        "@aws_c_common",
+        "@aws_checksums"
+    ],
+    visibility = ["//visibility:public"],
+)
diff --git a/cc/third_party/aws_checksums.BUILD.bazel b/cc/third_party/aws_checksums.BUILD.bazel
new file mode 100644
index 0000000..25fc636
--- /dev/null
+++ b/cc/third_party/aws_checksums.BUILD.bazel
@@ -0,0 +1,22 @@
+licenses(["notice"])  # Apache 2.0
+
+cc_library(
+    name = "aws_checksums",
+    srcs = glob([
+        "source/intel/*.c",
+        "source/*.c",
+    ]),
+    hdrs = glob([
+        "include/**/*.h"
+    ]),
+    includes = [
+        "include/",
+    ],
+    copts = [
+        "-O2" # Note there is some issue with the assembly implementation and GCC, therefore this should always be compiled with -O (https://github.com/awslabs/aws-checksums/issues/8)
+    ],
+    deps = [
+        "@aws_c_common",
+    ],
+    visibility = ["//visibility:public"],
+)
diff --git a/cc/third_party/aws_sdk_cpp.BUILD.bazel b/cc/third_party/aws_sdk_cpp.BUILD.bazel
index 6428b86..4fb7c1e 100644
--- a/cc/third_party/aws_sdk_cpp.BUILD.bazel
+++ b/cc/third_party/aws_sdk_cpp.BUILD.bazel
@@ -29,6 +29,9 @@
         "aws-cpp-sdk-core/source/utils/crypto/factory/**/*.cpp",
         "aws-cpp-sdk-kms/include/**/*.h",
         "aws-cpp-sdk-kms/source/**/*.cpp",
+        "aws-cpp-sdk-core/source/monitoring/*.cpp",
+        "aws-cpp-sdk-core/source/net/linux-shared/*.cpp",
+        "aws-cpp-sdk-core/source/utils/crypto/openssl/*.cpp",
     ]),
     hdrs = [
         "aws-cpp-sdk-core/include/aws/core/SDKConfig.h",
@@ -40,16 +43,20 @@
     # These must be in sync with version of aws_cpp_sdk in WORKSPACE.
     defines = [
         "AWS_SDK_VERSION_MAJOR=1",
-        "AWS_SDK_VERSION_MINOR=4",
-        "AWS_SDK_VERSION_PATCH=80",
+        "AWS_SDK_VERSION_MINOR=7",
+        "AWS_SDK_VERSION_PATCH=345",
         "ENABLE_CURL_CLIENT",
-        "ENABLE_NO_ENCRYPTION",
+        "ENABLE_OPENSSL_ENCRYPTION", # This is needed for UUID generation
+        "OPENSSL_IS_BORINGSSL",
         "PLATFORM_LINUX",
     ],
     visibility = ["//visibility:public"],
     strip_include_prefix = "aws-cpp-sdk-core/include",
     deps = [
+        "@aws_c_common",
+        "@aws_c_event_stream",
         "@curl",
+        "@boringssl//:crypto",
     ],
 )
 
diff --git a/cc/tink_cc_deps.bzl b/cc/tink_cc_deps.bzl
index fb8c1ea..9404a86 100644
--- a/cc/tink_cc_deps.bzl
+++ b/cc/tink_cc_deps.bzl
@@ -19,12 +19,12 @@
         )
 
     if not native.existing_rule("boring_ssl"):
-        # Commit from 2018-08-16
+        # Commit from 2020-06-23
         http_archive(
             name = "boringssl",
-            strip_prefix = "boringssl-18637c5f37b87e57ebde0c40fe19c1560ec88813",
-            url = "https://github.com/google/boringssl/archive/18637c5f37b87e57ebde0c40fe19c1560ec88813.zip",
-            sha256 = "bd923e59fca0d2b50db09af441d11c844c5e882a54c68943b7fc39a8cb5dd211",
+            strip_prefix = "boringssl-597b810379e126ae05d32c1d94b1a9464385acd0",
+            url = "https://github.com/google/boringssl/archive/597b810379e126ae05d32c1d94b1a9464385acd0.zip",
+            sha256 = "c4e8414cb36e62d2fee451296cc864f7ad1a4670396c8a67e1ee77ae84cc4167",
         )
 
     # GoogleTest/GoogleMock framework. Used by most C++ unit-tests.
@@ -48,15 +48,43 @@
         )
 
     if not native.existing_rule("aws_cpp_sdk"):
-        # Release from 2018-07-04
+        # Release from 2020-06-01
         http_archive(
             name = "aws_cpp_sdk",
             # Must be in sync with defines in third_party/aws_sdk_cpp.BUILD.bazel.
-            url = "https://github.com/aws/aws-sdk-cpp/archive/1.4.80.tar.gz",
-            strip_prefix = "aws-sdk-cpp-1.4.80",
+            url = "https://github.com/aws/aws-sdk-cpp/archive/1.7.345.tar.gz",
+            sha256 = "7df6491e6e0fac726c00b5e6298d5749b131b25a3dd8b905eb311dc7dcc97aaf",
+            strip_prefix = "aws-sdk-cpp-1.7.345",
             build_file = "@tink_cc//:third_party/aws_sdk_cpp.BUILD.bazel",
         )
 
+    if not native.existing_rule("aws_c_common"):
+        http_archive(
+            name = "aws_c_common",
+            url = "https://github.com/awslabs/aws-c-common/archive/v0.4.29.tar.gz",
+            sha256 = "01c2a58553a37b3aa5914d9e0bf7bf14507ff4937bc5872a678892ca20fcae1f",
+            strip_prefix = "aws-c-common-0.4.29",
+            build_file = "@tink_cc//:third_party/aws_c_common.BUILD.bazel",
+        )
+
+    if not native.existing_rule("aws_c_event_stream"):
+        http_archive(
+            name = "aws_c_event_stream",
+            url = "https://github.com/awslabs/aws-c-event-stream/archive/v0.1.4.tar.gz",
+            sha256 = "31d880d1c868d3f3df1e1f4b45e56ac73724a4dc3449d04d47fc0746f6f077b6",
+            strip_prefix = "aws-c-event-stream-0.1.4",
+            build_file = "@tink_cc//:third_party/aws_c_event_stream.BUILD.bazel",
+        )
+
+    if not native.existing_rule("aws_checksums"):
+        http_archive(
+            name = "aws_checksums",
+            url = "https://github.com/awslabs/aws-checksums/archive/v0.1.5.tar.gz",
+            sha256 = "6e6bed6f75cf54006b6bafb01b3b96df19605572131a2260fddaf0e87949ced0",
+            strip_prefix = "aws-checksums-0.1.5",
+            build_file = "@tink_cc//:third_party/aws_checksums.BUILD.bazel",
+        )
+
     # gRPC needs rules_apple, which in turn needs rules_swift and apple_support
     if not native.existing_rule("build_bazel_rules_apple"):
         # Last commit available at 2020-04-28
diff --git a/cc/util/BUILD.bazel b/cc/util/BUILD.bazel
index 9cee0b2..7a371d6 100644
--- a/cc/util/BUILD.bazel
+++ b/cc/util/BUILD.bazel
@@ -78,6 +78,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "@com_google_absl//absl/base:core_headers",
+        "@com_google_absl//absl/status",
         "@com_google_absl//absl/strings",
     ],
 )
@@ -192,6 +193,7 @@
         ":constants",
         ":enums",
         ":protobuf_helper",
+        ":secret_data",
         ":status",
         ":statusor",
         "//:aead",
diff --git a/cc/util/CMakeLists.txt b/cc/util/CMakeLists.txt
index 4708a6f..5dab20b 100644
--- a/cc/util/CMakeLists.txt
+++ b/cc/util/CMakeLists.txt
@@ -50,6 +50,7 @@
     status.h
   DEPS
     absl::base
+    absl::status
     absl::strings
   PUBLIC
 )
@@ -180,6 +181,7 @@
     tink::proto::tink_cc_proto
     tink::proto::xchacha20_poly1305_cc_proto
     tink::util::buffer
+    tink::util::secret_data
     absl::core_headers
     absl::memory
     absl::strings
diff --git a/cc/util/errors_test.cc b/cc/util/errors_test.cc
index bd799c1..cd0a0de 100644
--- a/cc/util/errors_test.cc
+++ b/cc/util/errors_test.cc
@@ -14,10 +14,11 @@
 //
 ///////////////////////////////////////////////////////////////////////////////
 
-#include "gtest/gtest.h"
 #include "tink/util/errors.h"
+
+#include "gtest/gtest.h"
+#include "absl/status/status.h"
 #include "tink/util/status.h"
-// placeholder_google3_status_header, please ignore
 
 namespace crypto {
 namespace tink {
@@ -41,7 +42,15 @@
   EXPECT_EQ(crypto::tink::util::error::UNKNOWN, status.error_code());
 }
 
-// placeholder_status_conversion_test, please ignore
+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());
+}
 
 }  // namespace
 }  // namespace tink
diff --git a/cc/util/status.cc b/cc/util/status.cc
index a962449..c2ac339 100644
--- a/cc/util/status.cc
+++ b/cc/util/status.cc
@@ -19,7 +19,7 @@
 #include "tink/util/status.h"
 
 #include "absl/strings/str_cat.h"
-// placeholder_google3_status_header, please ignore
+#include "absl/status/status.h"
 
 using ::std::ostream;
 
@@ -49,7 +49,17 @@
 
 }  // namespace
 
-// placeholder_implicit_type_conversion, please ignore
+Status::Status(const ::absl::Status& status)
+    : code_(::crypto::tink::util::error::OK) {
+  if (status.ok()) return;
+  code_ = static_cast<::crypto::tink::util::error::Code>(status.code());
+  message_ = std::string(status.message());
+}
+
+Status::operator ::absl::Status() const {
+  if (ok()) return ::absl::OkStatus();
+  return ::absl::Status(static_cast<absl::StatusCode>(code_), message_);
+}
 
 Status::Status() : code_(::crypto::tink::util::error::OK), message_("") {
 }
diff --git a/cc/util/status.h b/cc/util/status.h
index 70aee58..746912b 100644
--- a/cc/util/status.h
+++ b/cc/util/status.h
@@ -23,8 +23,7 @@
 #include <string>
 
 #include "absl/base/attributes.h"
-
-// placeholder_forward_declaration, please ignore
+#include "absl/status/status.h"
 
 namespace crypto {
 namespace tink {
@@ -171,7 +170,8 @@
 
   std::string ToString() const;
 
-  // placeholder_implicit_type_conversion, please ignore
+  Status(const ::absl::Status& status);
+  operator ::absl::Status() const;
 
  private:
   ::crypto::tink::util::error::Code code_;
diff --git a/cc/util/test_util.cc b/cc/util/test_util.cc
index 6273b13..c45c4ad 100644
--- a/cc/util/test_util.cc
+++ b/cc/util/test_util.cc
@@ -38,6 +38,7 @@
 #include "tink/subtle/subtle_util_boringssl.h"
 #include "tink/util/enums.h"
 #include "tink/util/protobuf_helper.h"
+#include "tink/util/secret_data.h"
 #include "tink/util/status.h"
 #include "tink/util/statusor.h"
 #include "proto/aes_ctr_hmac_aead.pb.h"
@@ -265,7 +266,8 @@
       Enums::ProtoToSubtle(curve_type)).ValueOrDie();
   EciesAeadHkdfPrivateKey ecies_key;
   ecies_key.set_version(0);
-  ecies_key.set_key_value(test_key.priv);
+  ecies_key.set_key_value(
+      std::string(util::SecretDataAsStringView(test_key.priv)));
   auto public_key = ecies_key.mutable_public_key();
   public_key->set_version(0);
   public_key->set_x(test_key.pub_x);
@@ -362,7 +364,8 @@
       Enums::ProtoToSubtle(curve_type)).ValueOrDie();
   EcdsaPrivateKey ecdsa_key;
   ecdsa_key.set_version(0);
-  ecdsa_key.set_key_value(test_key.priv);
+  ecdsa_key.set_key_value(
+      std::string(util::SecretDataAsStringView(test_key.priv)));
   auto public_key = ecdsa_key.mutable_public_key();
   public_key->set_version(0);
   public_key->set_x(test_key.pub_x);
diff --git a/cmake/TinkWorkspace.cmake b/cmake/TinkWorkspace.cmake
index 6edb2c7..8e589b7 100644
--- a/cmake/TinkWorkspace.cmake
+++ b/cmake/TinkWorkspace.cmake
@@ -61,8 +61,8 @@
 
 http_archive(
   NAME boringssl
-  URL https://github.com/google/boringssl/archive/18637c5f37b87e57ebde0c40fe19c1560ec88813.zip
-  SHA256 bd923e59fca0d2b50db09af441d11c844c5e882a54c68943b7fc39a8cb5dd211
+  URL https://github.com/google/boringssl/archive/597b810379e126ae05d32c1d94b1a9464385acd0.zip
+  SHA256 c4e8414cb36e62d2fee451296cc864f7ad1a4670396c8a67e1ee77ae84cc4167
   CMAKE_SUBDIR src
 )
 
diff --git a/docs/KNOWN-ISSUES.md b/docs/KNOWN-ISSUES.md
index 8a5b03b..5b87a20 100644
--- a/docs/KNOWN-ISSUES.md
+++ b/docs/KNOWN-ISSUES.md
@@ -3,6 +3,17 @@
 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.
+
 ## Java
 
 *   Tink supports Java 7 or newer. Please file a ticket if you want to support
diff --git a/docs/PYTHON-HOWTO.md b/docs/PYTHON-HOWTO.md
index 7d1596a..c6b2372 100644
--- a/docs/PYTHON-HOWTO.md
+++ b/docs/PYTHON-HOWTO.md
@@ -156,20 +156,23 @@
 encrypt or decrypt data:
 
 ```python
-  import tink
-  from tink import aead
+import tink
+from tink import aead
 
-  # Register all AEAD primitives
-  aead.register()
+plaintext = b'your data...'
+associated_data = b'context'
 
-  # 1. Get a handle to the key material.
-  keyset_handle = tink.new_keyset_handle(aead.aead_key_templates.AES256_GCM)
+# Register all AEAD primitives
+aead.register()
 
-  # 2. Get the primitive.
-  aead_primitive = keyset_handle.primitive(aead.Aead)
+# 1. Get a handle to the key material.
+keyset_handle = tink.new_keyset_handle(aead.aead_key_templates.AES256_GCM)
 
-  # 3. Use the primitive.
-  ciphertext = aead_primitive.encrypt(plaintext, associated data)
+# 2. Get the primitive.
+aead_primitive = keyset_handle.primitive(aead.Aead)
+
+# 3. Use the primitive.
+ciphertext = aead_primitive.encrypt(plaintext, associated_data)
 ```
 
 ### Deterministic symmetric key encryption
@@ -179,20 +182,23 @@
 primitive to encrypt or decrypt data:
 
 ```python
-  import tink
-  from tink import daead
+import tink
+from tink import daead
 
-  # Register all deterministic AEAD primitives
-  daead.register()
+plaintext = b'your data...'
+associated_data = b'context'
 
-  # 1. Get a handle to the key material.
-  keyset_handle = tink.new_keyset_handle(daead.deterministic_aead_key_templates.AES256_SIV)
+# Register all deterministic AEAD primitives
+daead.register()
 
-  # 2. Get the primitive.
-  daead_primitive = keyset_handle.primitive(daead.DeterministicAead)
+# 1. Get a handle to the key material.
+keyset_handle = tink.new_keyset_handle(daead.deterministic_aead_key_templates.AES256_SIV)
 
-  # 3. Use the primitive.
-  ciphertext = daead_primitive.encrypt_deterministically(plaintext, associated data)
+# 2. Get the primitive.
+daead_primitive = keyset_handle.primitive(daead.DeterministicAead)
+
+# 3. Use the primitive.
+ciphertext = daead_primitive.encrypt_deterministically(plaintext, associated_data)
 ```
 
 ### Message Authentication Code
@@ -201,23 +207,25 @@
 [MAC (Message Authentication Code)](PRIMITIVES.md#message-authentication-code):
 
 ```python
-  import tink
-  from tink import mac
+import tink
+from tink import mac
 
-  # Register all MAC primitives
-  mac.register()
+data = b'your data...'
 
-  # 1. Get a handle to the key material.
-  keyset_handle = tink.new_keyset_handle(mac.mac_key_templates.HMAC_SHA256_128BITTAG)
+# Register all MAC primitives
+mac.register()
 
-  # 2. Get the primitive.
-  mac = keyset_handle.primitive(mac.Mac)
+# 1. Get a handle to the key material.
+keyset_handle = tink.new_keyset_handle(mac.mac_key_templates.HMAC_SHA256_128BITTAG)
 
-  # 3. Use the primitive to compute a tag,
-  tag = mac.compute_mac(data)
+# 2. Get the primitive.
+mac = keyset_handle.primitive(mac.Mac)
 
-  # ... or to verify a tag.
-  mac.verify_mac(tag, data)
+# 3. Use the primitive to compute a tag,
+tag = mac.compute_mac(data)
+
+# ... or to verify a tag.
+mac.verify_mac(tag, data)
 ```
 
 ### Hybrid Encryption
@@ -227,34 +235,36 @@
 one can use the following:
 
 ```python
-  import tink
-  from tink import hybrid
+import tink
+from tink import hybrid
 
-  # Register all Hybrid primitives
-  hybrid.register()
+plaintext = b'your data...'
+context = b'context'
 
-  # 1. Generate the private key material.
-  private_keyset_handle = tink.new_keyset_handle(hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
+# Register all Hybrid primitives
+hybrid.register()
 
-  # Obtain the public key material.
-  public_keyset_handle = private_keyset_handle.public_keyset_handle()
+# 1. Generate the private key material.
+private_keyset_handle = tink.new_keyset_handle(hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
 
-  # Encryption
+# Obtain the public key material.
+public_keyset_handle = private_keyset_handle.public_keyset_handle()
 
-  # 2. Get the primitive.
-  hybrid_encrypt = public_keyset_handle.primitive(hybrid.HybridEncrypt)
+# Encryption
 
-  # 3. Use the primitive.
-  ciphertext = hybrid_encrypt.encrypt(plaintext, context)
+# 2. Get the primitive.
+hybrid_encrypt = public_keyset_handle.primitive(hybrid.HybridEncrypt)
 
-  # Decryption
+# 3. Use the primitive.
+ciphertext = hybrid_encrypt.encrypt(plaintext, context)
 
-  # 2. Get the primitive.
-  hybrid_decrypt = private_keyset_handle.primitive(hybrid.HybridDecrypt)
+# Decryption
 
-  # 3. Use the primitive.
-  plaintext = hybrid_decrypt.decrypt(ciphertext, context)
+# 2. Get the primitive.
+hybrid_decrypt = private_keyset_handle.primitive(hybrid.HybridDecrypt)
 
+# 3. Use the primitive.
+plaintext = hybrid_decrypt.decrypt(ciphertext, context)
 ```
 
 ### Digital Signatures
@@ -263,31 +273,31 @@
 [digital signature](PRIMITIVES.md#digital-signatures):
 
 ```python
-   import tink
-   from tink import signature
+import tink
+from tink import signature
 
-   # Register key manager for signatures
-   signature.register()
+# Register key manager for signatures
+signature.register()
 
-   # Signing
-   # 1. Generate the private key material.
-   keyset_handle = tink.new_keyset_handle(signature.signature_key_templates.ED25519)
+# Signing
+# 1. Generate the private key material.
+keyset_handle = tink.new_keyset_handle(signature.signature_key_templates.ED25519)
 
-   # 2. Get the primitive.
-   signer = keyset_handle.primitive(signature.PublicKeySign)
+# 2. Get the primitive.
+signer = keyset_handle.primitive(signature.PublicKeySign)
 
-   # 3. Use the primitive to sign.
-   signature = signer.sign(b'your data')
+# 3. Use the primitive to sign.
+signature_data = signer.sign(b'your data')
 
-   # Verifying
-   # 1. Obtain the public key material.
-   public_keyset_handle = keyset_handle.public_keyset_handle()
+# Verifying
+# 1. Obtain the public key material.
+public_keyset_handle = keyset_handle.public_keyset_handle()
 
-   # 2. Get the primitive.
-   verifier = public_keyset_handle.primitive(signature.PublicKeyVerify)
+# 2. Get the primitive.
+verifier = public_keyset_handle.primitive(signature.PublicKeyVerify)
 
-   # 3. Use the primitive to verify.
-   verifier.verify(signature, b'your data')
+# 3. Use the primitive to verify.
+verifier.verify(signature_data, b'your data')
 ```
 
 ### Envelope encryption
@@ -300,28 +310,32 @@
 using the credentials in `credentials.json` as follows:
 
 ```python
-  import tink
-  from tink import aead
-  from tink.integration import gcpkms
+import tink
+from tink import aead
+from tink.integration import gcpkms
 
-  key_uri = 'gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar'
-  gcp_credentials = 'credentials.json'
+key_uri = 'gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar'
+gcp_credentials = 'credentials.json'
 
-  # Read the GCP credentials and setup client
-  try:
-    gcp_client = gcpkms.GcpKmsClient(key_uri, gcp_credentials)
-    gcp_aead = gcp_client.get_aead(key_uri)
-  except tink.TinkError as e:
-    logging.error('Error initializing GCP client: %s', e)
-    return 1
+plaintext = b'your data...'
+associated_data = b'context'
 
-  # Create envelope AEAD primitive using AES256 GCM for encrypting the data
-  try:
-    key_template = aead.aead_key_templates.AES256_GCM
-    env_aead = aead.KmsEnvelopeAead(key_template, gcp_aead)
-  except tink.TinkError as e:
-    logging.error('Error creating primitive: %s', e)
-    return 1
-  # Use env_aead to encrypt data
-  ciphertext = env_aead.encrypt(plaintext, associated data)
+# Read the GCP credentials and setup client
+try:
+  gcp_client = gcpkms.GcpKmsClient(key_uri, gcp_credentials)
+  gcp_aead = gcp_client.get_aead(key_uri)
+except tink.TinkError as e:
+  logging.error('Error initializing GCP client: %s', e)
+  return 1
+
+# Create envelope AEAD primitive using AES256 GCM for encrypting the data
+try:
+  key_template = aead.aead_key_templates.AES256_GCM
+  env_aead = aead.KmsEnvelopeAead(key_template, gcp_aead)
+except tink.TinkError as e:
+  logging.error('Error creating primitive: %s', e)
+  return 1
+
+# Use env_aead to encrypt data
+ciphertext = env_aead.encrypt(plaintext, associated_data)
 ```
diff --git a/examples/java_src/helloworld/BUILD.bazel b/examples/java_src/helloworld/BUILD.bazel
index 878fcc9..22fc48f 100644
--- a/examples/java_src/helloworld/BUILD.bazel
+++ b/examples/java_src/helloworld/BUILD.bazel
@@ -16,9 +16,13 @@
     ],
     deps = [
         "@maven//:args4j_args4j",
-        "@tink_java//:cleartext_keyset_handle",
-        "@tink_java//:java",
-        "@tink_java//:subtle",
+        "@tink_java//src/main/java/com/google/crypto/tink:aead",
+        "@tink_java//src/main/java/com/google/crypto/tink:cleartext_keyset_handle",
+        "@tink_java//src/main/java/com/google/crypto/tink:json_keyset_reader",
+        "@tink_java//src/main/java/com/google/crypto/tink:json_keyset_writer",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
+        "@tink_java//src/main/java/com/google/crypto/tink/aead:aead_config",
+        "@tink_java//src/main/java/com/google/crypto/tink/aead:aead_key_templates",
     ],
 )
 
diff --git a/go/aead/aead.go b/go/aead/aead.go
index 3398eae..e93b616 100644
--- a/go/aead/aead.go
+++ b/go/aead/aead.go
@@ -15,38 +15,6 @@
 // Package aead provides implementations of the AEAD primitive.
 //
 // AEAD encryption assures the confidentiality and authenticity of the data. This primitive is CPA secure.
-//
-// Example:
-//
-//   package main
-//
-//   import (
-//       "fmt"
-//
-//       "github.com/google/tink/go/aead"
-//       "github.com/google/tink/go/keyset"
-//   )
-//
-//   func main() {
-//
-//       kh, err := keyset.NewHandle(aead.AES256GCMKeyTemplate())
-//       if err != nil {
-//           // handle the error
-//       }
-//
-//       a := aead.New(kh)
-//
-//       ct , err := a.Encrypt([]byte("this data needs to be encrypted"), []byte("associated data"))
-//       if err != nil {
-//           // handle error
-//       }
-//
-//       pt, err := a.Decrypt(ct, []byte("associated data"))
-//       if err != nil {
-//           //handle error
-//       }
-//
-//   }
 package aead
 
 import (
diff --git a/go/aead/aead_factory.go b/go/aead/aead_factory.go
index b5efd11..2873753 100644
--- a/go/aead/aead_factory.go
+++ b/go/aead/aead_factory.go
@@ -95,7 +95,8 @@
 		ctNoPrefix := ct[prefixSize:]
 		entries, err := a.ps.EntriesForPrefix(string(prefix))
 		if err == nil {
-			for i := 0; i < len(entries); i++ {
+			// Attempt to decrypt with newer keys first because they more likely are the correct one.
+			for i := len(entries) - 1; i >= 0; i-- {
 				p, ok := (entries[i].Primitive).(tink.AEAD)
 				if !ok {
 					return nil, fmt.Errorf("aead_factory: not an AEAD primitive")
@@ -111,7 +112,7 @@
 	// try raw keys
 	entries, err := a.ps.RawEntries()
 	if err == nil {
-		for i := 0; i < len(entries); i++ {
+		for i := len(entries) - 1; i >= 0; i-- {
 			p, ok := (entries[i].Primitive).(tink.AEAD)
 			if !ok {
 				return nil, fmt.Errorf("aead_factory: not an AEAD primitive")
diff --git a/go/aead/aead_test.go b/go/aead/aead_test.go
index f9ca3ae..7220bcc 100644
--- a/go/aead/aead_test.go
+++ b/go/aead/aead_test.go
@@ -15,13 +15,40 @@
 package aead_test
 
 import (
+	"log"
 	"testing"
 
+	"github.com/google/tink/go/aead"
 	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/keyset"
 	"github.com/google/tink/go/testutil"
 )
 
-func TestAeadInit(t *testing.T) {
+func Example() {
+	kh, err := keyset.NewHandle(aead.AES256GCMKeyTemplate())
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	a, err := aead.New(kh)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	ct, err := a.Encrypt([]byte("this data needs to be encrypted"), []byte("this data needs to be authenticated, but not encrypted"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	_, err = a.Decrypt(ct, []byte("this data needs to be authenticated, but not encrypted"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Output:
+}
+
+func TestAEADInit(t *testing.T) {
 	// Check for AES-GCM key manager.
 	_, err := registry.GetKeyManager(testutil.AESGCMTypeURL)
 	if err != nil {
diff --git a/go/aead/aes_ctr_hmac_aead_key_manager.go b/go/aead/aes_ctr_hmac_aead_key_manager.go
index 87ad066..26497fd 100644
--- a/go/aead/aes_ctr_hmac_aead_key_manager.go
+++ b/go/aead/aes_ctr_hmac_aead_key_manager.go
@@ -139,11 +139,15 @@
 
 // validateKey validates the given AesCtrHmacAeadKey proto.
 func (km *aesCTRHMACAEADKeyManager) validateKey(key *aeadpb.AesCtrHmacAeadKey) error {
-	err := keyset.ValidateKeyVersion(key.Version, aesCTRHMACAEADKeyVersion)
-	if err != nil {
+	if err := keyset.ValidateKeyVersion(key.Version, aesCTRHMACAEADKeyVersion); err != nil {
 		return fmt.Errorf("aes_ctr_hmac_aead_key_manager: %v", err)
 	}
-
+	if err := keyset.ValidateKeyVersion(key.AesCtrKey.Version, aesCTRHMACAEADKeyVersion); err != nil {
+		return fmt.Errorf("aes_ctr_hmac_aead_key_manager: %v", err)
+	}
+	if err := keyset.ValidateKeyVersion(key.HmacKey.Version, aesCTRHMACAEADKeyVersion); err != nil {
+		return fmt.Errorf("aes_ctr_hmac_aead_key_manager: %v", err)
+	}
 	// Validate AesCtrKey.
 	keySize := uint32(len(key.AesCtrKey.KeyValue))
 	if err := subtle.ValidateAESKeySize(keySize); err != nil {
diff --git a/go/daead/aes_siv_key_manager.go b/go/daead/aes_siv_key_manager.go
index 4b0227f..82c1f0f 100644
--- a/go/daead/aes_siv_key_manager.go
+++ b/go/daead/aes_siv_key_manager.go
@@ -60,17 +60,34 @@
 	return ret, nil
 }
 
-// NewKey creates a new key, ignoring the specification in the given serialized key format
-// because the key size and other params are fixed.
+// NewKey creates a new key. serializedKeyFormat is not required, because there is only one
+// valid key format.
 func (km *aesSIVKeyManager) NewKey(serializedKeyFormat []byte) (proto.Message, error) {
-	return km.newAesSivKey(), nil
+	if serializedKeyFormat != nil {
+		keyFormat := new(aspb.AesSivKeyFormat)
+		if err := proto.Unmarshal(serializedKeyFormat, keyFormat); err != nil {
+			return nil, fmt.Errorf("aes_siv_key_manager: invalid key format")
+		}
+		if keyFormat.KeySize != subtle.AESSIVKeySize {
+			return nil, fmt.Errorf("aes_siv_key_manager: keyFormat.KeySize != %d", subtle.AESSIVKeySize)
+		}
+	}
+	keyValue := random.GetRandomBytes(subtle.AESSIVKeySize)
+	key := &aspb.AesSivKey{
+		Version:  aesSIVKeyVersion,
+		KeyValue: keyValue,
+	}
+	return key, nil
 }
 
-// NewKeyData creates a new KeyData, ignoring the specification in the given serialized key format
-// because the key size and other params are fixed.
+// NewKeyData creates a new KeyData. serializedKeyFormat is not required, because there is only one
+// valid key format.
 // It should be used solely by the key management API.
 func (km *aesSIVKeyManager) NewKeyData(serializedKeyFormat []byte) (*tinkpb.KeyData, error) {
-	key := km.newAesSivKey()
+	key, err := km.NewKey(serializedKeyFormat)
+	if err != nil {
+		return nil, err
+	}
 	serializedKey, err := proto.Marshal(key)
 	if err != nil {
 		return nil, err
@@ -104,12 +121,3 @@
 	}
 	return nil
 }
-
-// newAesSivKey creates a new AesSivKey.
-func (km *aesSIVKeyManager) newAesSivKey() *aspb.AesSivKey {
-	keyValue := random.GetRandomBytes(subtle.AESSIVKeySize)
-	return &aspb.AesSivKey{
-		Version:  aesSIVKeyVersion,
-		KeyValue: keyValue,
-	}
-}
diff --git a/go/daead/aes_siv_key_manager_test.go b/go/daead/aes_siv_key_manager_test.go
index ae4a165..3284b62 100644
--- a/go/daead/aes_siv_key_manager_test.go
+++ b/go/daead/aes_siv_key_manager_test.go
@@ -102,6 +102,24 @@
 	}
 }
 
+func TestAESSIVNewKeyInvalid(t *testing.T) {
+	km, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
+	if err != nil {
+		t.Errorf("cannot obtain AESSIV key manager: %s", err)
+	}
+	keyFormat := &aspb.AesSivKeyFormat{
+		KeySize: subtle.AESSIVKeySize - 1,
+	}
+	serializedKeyFormat, err := proto.Marshal(keyFormat)
+	if err != nil {
+		t.Errorf("proto.Marshal(keyFormat) = %v; want nil", err)
+	}
+	_, err = km.NewKey(serializedKeyFormat)
+	if err == nil {
+		t.Errorf("km.NewKey(serializedKeyFormat) = _, nil; want _, err")
+	}
+}
+
 func TestAESSIVDoesSupport(t *testing.T) {
 	km, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
 	if err != nil {
diff --git a/go/daead/daead.go b/go/daead/daead.go
index 3342188..69ca616 100644
--- a/go/daead/daead.go
+++ b/go/daead/daead.go
@@ -16,45 +16,6 @@
 //
 // Unlike AEAD, implementations of this interface are not semantically secure, because
 // encrypting the same plaintex always yields the same ciphertext.
-//
-// Example:
-//
-//   package main
-//
-//   import (
-//       "fmt"
-//
-//       "github.com/google/tink/go/daead"
-//       "github.com/google/tink/go/keyset"
-//   )
-//
-//   func main() {
-//
-//       kh, err := keyset.NewHandle(daead.AESSIVKeyTemplate())
-//       if err != nil {
-//           // handle the error
-//       }
-//
-//       d := daead.New(kh)
-//
-//       ct1 , err := d.EncryptDeterministically([]byte("this data needs to be encrypted"), []byte("additional data"))
-//       if err != nil {
-//           // handle error
-//       }
-//
-//       pt , err := d.DecryptDeterministically(ct, []byte("additional data"))
-//       if err != nil {
-//           // handle error
-//       }
-//
-//       ct2 , err := d.EncryptDeterministically([]byte("this data needs to be encrypted"), []byte("additional data"))
-//       if err != nil {
-//           // handle error
-//       }
-//
-//       // ct1 will be equal to ct2
-//
-//   }
 package daead
 
 import (
diff --git a/go/daead/daead_test.go b/go/daead/daead_test.go
index 4c6b852..40fabb6 100644
--- a/go/daead/daead_test.go
+++ b/go/daead/daead_test.go
@@ -15,12 +15,49 @@
 package daead_test
 
 import (
+	"bytes"
+	"log"
 	"testing"
 
 	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/daead"
+	"github.com/google/tink/go/keyset"
 	"github.com/google/tink/go/testutil"
 )
 
+func Example() {
+	kh, err := keyset.NewHandle(daead.AESSIVKeyTemplate())
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	d, err := daead.New(kh)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	ct1, err := d.EncryptDeterministically([]byte("this data needs to be encrypted"), []byte("this data needs to be authenticated, but not encrypted"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	_, err = d.DecryptDeterministically(ct1, []byte("this data needs to be authenticated, but not encrypted"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	ct2, err := d.EncryptDeterministically([]byte("this data needs to be encrypted"), []byte("this data needs to be authenticated, but not encrypted"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	if !bytes.Equal(ct1, ct2) {
+		log.Fatal("ct1 != ct2")
+	}
+
+	// Output:
+}
+
 func TestDeterministicAEADInit(t *testing.T) {
 	// Check for AES-SIV key manager.
 	_, err := registry.GetKeyManager(testutil.AESSIVTypeURL)
diff --git a/go/hybrid/BUILD.bazel b/go/hybrid/BUILD.bazel
index f4d78f2..5b2badf 100644
--- a/go/hybrid/BUILD.bazel
+++ b/go/hybrid/BUILD.bazel
@@ -37,11 +37,14 @@
         "ecies_aead_hkdf_hybrid_encrypt_test.go",
         "hybrid_factory_test.go",
         "hybrid_key_templates_test.go",
+        "hybrid_test.go",
         "register_ecies_aead_hkdf_dem_helper_test.go",
     ],
     embed = [":go_default_library"],
     deps = [
         "//aead:go_default_library",
+        "//hybrid/subtle:go_default_library",
+        "//keyset:go_default_library",
         "//mac:go_default_library",
         "//proto:common_go_proto",
         "//proto:ecies_aead_hkdf_go_proto",
diff --git a/go/hybrid/ecies_aead_hkdf_private_key_manager.go b/go/hybrid/ecies_aead_hkdf_private_key_manager.go
index e3ac833..1359126 100644
--- a/go/hybrid/ecies_aead_hkdf_private_key_manager.go
+++ b/go/hybrid/ecies_aead_hkdf_private_key_manager.go
@@ -17,12 +17,12 @@
 import (
 	"errors"
 	"fmt"
-	"strings"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/google/tink/go/core/registry"
 	"github.com/google/tink/go/hybrid/subtle"
 	"github.com/google/tink/go/keyset"
+	commonpb "github.com/google/tink/go/proto/common_go_proto"
 	eahpb "github.com/google/tink/go/proto/ecies_aead_hkdf_go_proto"
 	tinkpb "github.com/google/tink/go/proto/tink_go_proto"
 )
@@ -171,11 +171,11 @@
 	if err != nil {
 		return err
 	}
-	if strings.Compare(params.KemParams.HkdfHashType.String(), "HashType_UNKNOWN_HASH") == 0 {
+	if params.KemParams.HkdfHashType == commonpb.HashType_UNKNOWN_HASH {
 		return errors.New("hash unsupported for HMAC")
 	}
 
-	if strings.Compare(params.EcPointFormat.String(), "EcPointFormat_UNKNOWN_FORMAT") == 0 {
+	if params.EcPointFormat == commonpb.EcPointFormat_UNKNOWN_FORMAT {
 		return errors.New("unknown EC point format")
 	}
 	km, err := registry.GetKeyManager(params.DemParams.AeadDem.TypeUrl)
diff --git a/go/hybrid/hybrid.go b/go/hybrid/hybrid.go
index 2fb9570..305b6e5 100644
--- a/go/hybrid/hybrid.go
+++ b/go/hybrid/hybrid.go
@@ -27,38 +27,6 @@
 // context, but should be bound to the resulting ciphertext, i.e. the
 // ciphertext allows for checking the integrity of contextInfo (but there are
 // no guarantees wrt. the secrecy or authenticity of contextInfo).
-//
-// Example:
-//
-//   package main
-//
-//   import (
-//       "github.com/google/tink/go/hybrid"
-//       "github.com/google/tink/go/core/registry"
-//       "github.com/google/tink/go/keyset"
-//   )
-//
-//   func main() {
-//
-//       kh , err := keyset.NewHandle(hybrid.ECIESHKDFAES128CTRHMACSHA256KeyTemplate())
-//       if err != nil {
-//           //handle error
-//       }
-//       h := hybrid.NewHybridEncrypt(kh)
-//
-//       ct, err = h.Encrypt([]byte("secret message"), []byte("context info"))
-//       if err != nil {
-//           // handle error
-//       }
-//
-//       khd , err := keyset.NewHandle( .....); /// get a handle on the decryption key material
-//       hd := hybrid.NewHybridDecrypt(khd)
-//
-//       pt, err := hd.Decrypt(ct, []byte("context info"))
-//       if err != nil {
-//           // handle error
-//       }
-//   }
 package hybrid
 
 import (
diff --git a/go/hybrid/hybrid_test.go b/go/hybrid/hybrid_test.go
new file mode 100644
index 0000000..dfe2235
--- /dev/null
+++ b/go/hybrid/hybrid_test.go
@@ -0,0 +1,56 @@
+// 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 hybrid_test
+
+import (
+	"log"
+
+	"github.com/google/tink/go/hybrid"
+	"github.com/google/tink/go/keyset"
+)
+
+func Example() {
+	khPriv, err := keyset.NewHandle(hybrid.ECIESHKDFAES128CTRHMACSHA256KeyTemplate())
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	khPub, err := khPriv.Public()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	enc, err := hybrid.NewHybridEncrypt(khPub)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	ct, err := enc.Encrypt([]byte("this data needs to be encrypted"), []byte("context info"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	dec, err := hybrid.NewHybridDecrypt(khPriv)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	_, err = dec.Decrypt(ct, []byte("context info"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Output:
+}
diff --git a/go/integration/gcpkms/BUILD.bazel b/go/integration/gcpkms/BUILD.bazel
index 92ceaa3..6ad4f6a 100644
--- a/go/integration/gcpkms/BUILD.bazel
+++ b/go/integration/gcpkms/BUILD.bazel
@@ -23,7 +23,10 @@
 
 go_test(
     name = "go_default_test",
-    srcs = ["gcp_kms_aead_test.go"],
+    srcs = [
+        "gcp_kms_aead_test.go",
+        "gcp_kms_client_test.go",
+    ],
     data = [
         "@tink_base//testdata:credentials",
         "@tink_base//testdata:ecies_keysets",
diff --git a/go/integration/gcpkms/gcp_kms_client.go b/go/integration/gcpkms/gcp_kms_client.go
index 4c0c1cc..b2539d4 100644
--- a/go/integration/gcpkms/gcp_kms_client.go
+++ b/go/integration/gcpkms/gcp_kms_client.go
@@ -16,48 +16,6 @@
 
 // Package gcpkms provides integration with the GCP Cloud KMS.
 // Tink APIs work with GCP and AWS KMS.
-// GCP Example below:
-//
-// package main
-//
-// import (
-//     "github.com/google/tink/go/aead"
-//     "github.com/google/tink/go/core/registry"
-//     "github.com/google/tink/go/integration/gcpkms"
-//     "github.com/google/tink/go/keyset"
-// )
-//
-// const (
-//     keyURI = "gcp-kms://......"
-// )
-//
-// func main() {
-//     gcpclient, err := gcpkms.NewClientWithCredentials(keyURI, "/mysecurestorage/credentials.json")
-//     if err != nil {
-//         //handle error
-//     }
-//     registry.RegisterKMSClient(gcpclient)
-//
-//     dek := aead.AES128CTRHMACSHA256KeyTemplate()
-//     kh, err := keyset.NewHandle(aead.KMSEnvelopeAEADKeyTemplate(keyURI, dek))
-//     if err != nil {
-//         // handle error
-//     }
-//     a, err := aead.New(kh)
-//     if err != nil {
-//         // handle error
-//     }
-//
-//     ct, err = a.Encrypt([]byte("secret message"), []byte("associated data"))
-//     if err != nil {
-//         // handle error
-//     }
-//
-//     pt, err = a.Decrypt(ct, []byte("associated data"))
-//     if err != nil {
-//         // handle error
-//     }
-// }
 package gcpkms
 
 import (
diff --git a/go/integration/gcpkms/gcp_kms_client_test.go b/go/integration/gcpkms/gcp_kms_client_test.go
new file mode 100644
index 0000000..d8bb8c4
--- /dev/null
+++ b/go/integration/gcpkms/gcp_kms_client_test.go
@@ -0,0 +1,55 @@
+// 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 gcpkms
+
+import (
+	"log"
+
+	"github.com/google/tink/go/aead"
+	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/keyset"
+)
+
+func Example() {
+	const keyURI = "gcp-kms://......"
+
+	gcpclient, err := NewClientWithCredentials(keyURI, "/mysecurestorage/credentials.json")
+	if err != nil {
+		log.Fatal(err)
+	}
+	registry.RegisterKMSClient(gcpclient)
+
+	dek := aead.AES128CTRHMACSHA256KeyTemplate()
+	kh, err := keyset.NewHandle(aead.KMSEnvelopeAEADKeyTemplate(keyURI, dek))
+	if err != nil {
+		log.Fatal(err)
+	}
+	a, err := aead.New(kh)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	ct, err := a.Encrypt([]byte("this data needs to be encrypted"), []byte("this data needs to be authenticated, but not encrypted"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	_, err = a.Decrypt(ct, []byte("this data needs to be authenticated, but not encrypted"))
+	if err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/go/integration/hcvault/BUILD.bazel b/go/integration/hcvault/BUILD.bazel
index 3cfca49..624b48e 100644
--- a/go/integration/hcvault/BUILD.bazel
+++ b/go/integration/hcvault/BUILD.bazel
@@ -20,7 +20,15 @@
 
 go_test(
     name = "go_default_test",
-    srcs = ["hcvault_aead_test.go"],
+    srcs = [
+        "hcvault_aead_test.go",
+        "hcvault_client_test.go",
+    ],
     data = ["//integration/hcvault/testdata:server_tls_files"],
     embed = [":go_default_library"],
+    deps = [
+        "//aead:go_default_library",
+        "//core/registry:go_default_library",
+        "//keyset:go_default_library",
+    ]
 )
diff --git a/go/integration/hcvault/hcvault_client.go b/go/integration/hcvault/hcvault_client.go
index 55d8b81..5e5d31d 100644
--- a/go/integration/hcvault/hcvault_client.go
+++ b/go/integration/hcvault/hcvault_client.go
@@ -15,55 +15,6 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 // Package hcvault provides integration with the HashiCorp Vault (https://www.vaultproject.io/).
-// Below there is an example of how the integration code can be used:
-
-// package main
-//
-// import (
-// 	"fmt"
-// 	"log"
-//
-//  "github.com/google/tink/go/aead"
-//  "github.com/google/tink/go/core/registry"
-//  "github.com/google/tink/go/integration/hcvault"
-//  "github.com/google/tink/go/keyset"
-// )
-//
-// const (
-// 	keyURI = "hcvault://hcvault.corp.com:8200/transit/keys/key-1"
-// )
-//
-// func main() {
-//  tlsConf := getTLSConfig()
-//  token := getVaultToken()
-//  vaultClient, err := hcvault.NewClient(keyURI, tlsConf, token)
-// 	if err != nil {
-//    // handle error
-// 	}
-// 	registry.RegisterKMSClient(vaultClient)
-//
-// 	dek := aead.AES128CTRHMACSHA256KeyTemplate()
-// 	kh, err := keyset.NewHandle(aead.KMSEnvelopeAEADKeyTemplate(keyURI, dek))
-// 	if err != nil {
-//    // handle error
-// 	}
-// 	a, err := aead.New(kh)
-// 	if err != nil {
-//    // handle error
-// 	}
-//
-// 	msg := "secret message"
-// 	ct, err := a.Encrypt([]byte(msg), nil)
-// 	if err != nil {
-//    // handle error
-// 	}
-//
-// 	pt, err := a.Decrypt(ct, nil)
-// 	if err != nil {
-//    // handle error
-// 	}
-// }
-
 package hcvault
 
 import (
diff --git a/go/integration/hcvault/hcvault_client_test.go b/go/integration/hcvault/hcvault_client_test.go
new file mode 100644
index 0000000..d5c9a25
--- /dev/null
+++ b/go/integration/hcvault/hcvault_client_test.go
@@ -0,0 +1,65 @@
+// Copyright 2019 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 hcvault
+
+import (
+	"crypto/tls"
+	"log"
+
+	"github.com/google/tink/go/aead"
+	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/keyset"
+)
+
+func Example() {
+	const keyURI = "hcvault://hcvault.corp.com:8200/transit/keys/key-1"
+
+	vaultClient, err := NewClient(keyURI, tlsConfig(), vaultToken())
+	if err != nil {
+		log.Fatal(err)
+	}
+	registry.RegisterKMSClient(vaultClient)
+
+	dek := aead.AES128CTRHMACSHA256KeyTemplate()
+	kh, err := keyset.NewHandle(aead.KMSEnvelopeAEADKeyTemplate(keyURI, dek))
+	if err != nil {
+		log.Fatal(err)
+	}
+	a, err := aead.New(kh)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	ct, err := a.Encrypt([]byte("this data needs to be encrypted"), nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	_, err = a.Decrypt(ct, nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func tlsConfig() *tls.Config {
+	// Return a TLS configuration used to communicate with Vault server via HTTPS.
+	return nil
+}
+
+func vaultToken() string {
+	return "" // Your Vault token.
+}
diff --git a/go/mac/mac.go b/go/mac/mac.go
index 873e549..e6045ef 100644
--- a/go/mac/mac.go
+++ b/go/mac/mac.go
@@ -17,37 +17,6 @@
 // MAC computes a tag for a given message that can be used to authenticate a
 // message.  MAC protects data integrity as well as provides for authenticity
 // of the message.
-//
-// Example:
-//
-//   package main
-//
-//   import (
-//       "fmt"
-//
-//       "github.com/google/tink/go/mac"
-//       "github.com/google/tink/go/keyset"
-//   )
-//
-//   func main() {
-//
-//       kh, err := keyset.NewHandle(mac.HMACSHA256Tag256KeyTemplate())
-//       if err != nil {
-//           // handle the error
-//       }
-//
-//       m := mac.New(kh)
-//
-//       mac , err := m.ComputeMac([]byte("this data needs to be MACed"))
-//       if err != nil {
-//           // handle error
-//       }
-//
-//       if m.VerifyMAC(mac, []byte("this data needs to be MACed")); err != nil {
-//           //handle error
-//       }
-//
-//   }
 package mac
 
 import (
diff --git a/go/mac/mac_test.go b/go/mac/mac_test.go
index b9c139e..bf2280e 100644
--- a/go/mac/mac_test.go
+++ b/go/mac/mac_test.go
@@ -15,9 +15,12 @@
 package mac_test
 
 import (
+	"log"
 	"testing"
 
 	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/mac"
 	"github.com/google/tink/go/testutil"
 )
 
@@ -31,3 +34,26 @@
 		t.Errorf("unexpected error: %s", err)
 	}
 }
+
+func Example() {
+	kh, err := keyset.NewHandle(mac.HMACSHA256Tag256KeyTemplate())
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	m, err := mac.New(kh)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	mac, err := m.ComputeMAC([]byte("this data needs to be MACed"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	if m.VerifyMAC(mac, []byte("this data needs to be MACed")); err != nil {
+		log.Fatal(err)
+	}
+
+	// Output:
+}
diff --git a/go/mac/subtle/hmac.go b/go/mac/subtle/hmac.go
index 99fb5b6..d969ca9 100644
--- a/go/mac/subtle/hmac.go
+++ b/go/mac/subtle/hmac.go
@@ -32,13 +32,6 @@
 	minTagSizeInBytes = uint32(10)
 )
 
-// Maximum tag size in bytes for each hash type
-var maxTagSizeInBytes = map[string]uint32{
-	"SHA1":   uint32(20),
-	"SHA256": uint32(32),
-	"SHA512": uint32(64),
-}
-
 var errHMACInvalidInput = errors.New("HMAC: invalid input")
 
 // HMAC implementation of interface tink.MAC
@@ -68,11 +61,11 @@
 // ValidateHMACParams validates parameters of HMAC constructor.
 func ValidateHMACParams(hash string, keySize uint32, tagSize uint32) error {
 	// validate tag size
-	maxTagSize, found := maxTagSizeInBytes[hash]
-	if !found {
-		return fmt.Errorf("invalid hash algorithm")
+	digestSize, err := subtle.GetHashDigestSize(hash)
+	if err != nil {
+		return err
 	}
-	if tagSize > maxTagSize {
+	if tagSize > digestSize {
 		return fmt.Errorf("tag size too big")
 	}
 	if tagSize < minTagSizeInBytes {
diff --git a/go/signature/signature.go b/go/signature/signature.go
index bfd7458..cfc2c2a 100644
--- a/go/signature/signature.go
+++ b/go/signature/signature.go
@@ -16,49 +16,6 @@
 // primitives.
 //
 // To sign data using Tink you can use ECDSA or ED25519 key templates.
-//
-// Example:
-//
-//   package main
-//
-//   import (
-//       "fmt"
-//
-//       "github.com/google/tink/go/signature"
-//       "github.com/google/tink/go/keyset"
-//   )
-//
-//   func main() {
-//
-//       kh, err := keyset.NewHandle(signature.ECDSAP256KeyTemplate()) // other key templates can also be used
-//       if err != nil {
-//           // handle the error
-//       }
-//
-//       s, err := signature.NewSigner(kh)
-//	     if err != nil {
-//		     // handle error
-//       }
-//
-//       a , err := s.Sign([]byte("this data needs to be signed"))
-//       if err != nil {
-//           // handle the error
-//       }
-//
-//       pubkh, err := kh.Public()
-//	     if err != nil {
-//           // handle the error
-//       }
-//
-//       v, err := signature.NewVerifier(pubkh)
-//	     if err != nil {
-//           // handle the error
-//       }
-//
-//       if err := v.Verify(a, []byte("this data needs to be signed")); err != nil {
-//           // handle the error
-//       }
-//   }
 package signature
 
 import (
diff --git a/go/signature/signature_test.go b/go/signature/signature_test.go
index cc8cdbc..55ef2a2 100644
--- a/go/signature/signature_test.go
+++ b/go/signature/signature_test.go
@@ -15,9 +15,12 @@
 package signature_test
 
 import (
+	"log"
 	"testing"
 
 	"github.com/google/tink/go/core/registry"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/signature"
 	"github.com/google/tink/go/testutil"
 )
 
@@ -34,3 +37,36 @@
 		t.Errorf("unexpected error: %s", err)
 	}
 }
+
+func Example() {
+	kh, err := keyset.NewHandle(signature.ECDSAP256KeyTemplate()) // Other key templates can also be used.
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	s, err := signature.NewSigner(kh)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	a, err := s.Sign([]byte("this data needs to be signed"))
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	pubkh, err := kh.Public()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	v, err := signature.NewVerifier(pubkh)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	if err := v.Verify(a, []byte("this data needs to be signed")); err != nil {
+		log.Fatal(err)
+	}
+
+	// Output:
+}
diff --git a/go/streamingaead/aes_gcm_hkdf_key_manager.go b/go/streamingaead/aes_gcm_hkdf_key_manager.go
index 61b917b..8359275 100644
--- a/go/streamingaead/aes_gcm_hkdf_key_manager.go
+++ b/go/streamingaead/aes_gcm_hkdf_key_manager.go
@@ -150,7 +150,7 @@
 	if params.HkdfHashType == commonpb.HashType_UNKNOWN_HASH {
 		return errors.New("unknown HKDF hash type")
 	}
-	if params.CiphertextSegmentSize < params.DerivedKeySize+subtle.NoncePrefixInBytes+subtle.TagSizeInBytes+2 {
+	if params.CiphertextSegmentSize < params.DerivedKeySize+subtle.AESGCMHKDFNoncePrefixInBytes+subtle.AESGCMHKDFTagSizeInBytes+2 {
 		return fmt.Errorf("ciphertext segment_size must be at least (derived_key_size + noncePrefixInBytes + " +
 			"tagSizeInBytes + 2")
 	}
diff --git a/go/streamingaead/streamingaead.go b/go/streamingaead/streamingaead.go
index 4932f90..232cf83 100644
--- a/go/streamingaead/streamingaead.go
+++ b/go/streamingaead/streamingaead.go
@@ -14,107 +14,8 @@
 
 // Package streamingaead provides implementations of the streaming AEAD primitive.
 //
-// AEAD encryption assures the confidentiality and authenticity of the data. This primitive is CPA secure.
-//
-// Example:
-//
-// import (
-// 	"io"
-// 	"io/ioutil"
-// 	"log"
-// 	"os"
-// 	"path/filepath"
-//
-// 	"github.com/google/tink/go/aead"
-// 	"github.com/google/tink/go/keyset"
-// 	"github.com/google/tink/go/streamingaead"
-// )
-//
-// func main() {
-// 	dir, err := ioutil.TempDir("", "streamingaead")
-// 	if err != nil {
-// 		log.Fatal(err)
-// 	}
-// 	defer os.RemoveAll(dir)
-//
-// 	var (
-// 		srcFilename = filepath.Join(dir, "plaintext.src")
-// 		ctFilename  = filepath.Join(dir, "ciphertext.bin")
-// 		dstFilename = filepath.Join(dir, "plaintext.dst")
-// 	)
-//
-// 	if err := ioutil.WriteFile(srcFilename, []byte("this data needs to be encrypted"), 0666); err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	kh, err := keyset.NewHandle(streamingaead.AES256GCMHKDF4KBKeyTemplate())
-// 	if err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	a, err := streamingaead.New(kh)
-// 	if err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	srcFile, err := os.Open(srcFilename)
-// 	if err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	ctFile, err := os.Create(ctFilename)
-// 	if err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	w, err := a.NewEncryptingWriter(ctFile, []byte("associated data"))
-// 	if err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	if _, err := io.Copy(w, srcFile); err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	if err := w.Close(); err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	if err := ctFile.Close(); err != nil {
-// 		log.Fatal(err)
-// 	}
-// 	if err := srcFile.Close(); err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	// Decrypt encrypted file.
-//
-// 	ctFile, err = os.Open(ctFilename)
-// 	if err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	dstFile, err := os.Create(dstFilename)
-// 	if err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	r, err := a.NewDecryptingReader(ctFile, []byte("associated data"))
-// 	if err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	if _, err := io.Copy(dstFile, r); err != nil {
-// 		log.Fatal(err)
-// 	}
-//
-// 	if err := dstFile.Close(); err != nil {
-// 		log.Fatal(err)
-// 	}
-// 	if err := ctFile.Close(); err != nil {
-// 		log.Fatal(err)
-// 	}
-// }
+// AEAD encryption assures the confidentiality and authenticity of the data.
+// This primitive is CPA secure.
 package streamingaead
 
 import (
diff --git a/go/streamingaead/subtle/aes_gcm_hkdf.go b/go/streamingaead/subtle/aes_gcm_hkdf.go
index 14b9a01..26d30ca 100644
--- a/go/streamingaead/subtle/aes_gcm_hkdf.go
+++ b/go/streamingaead/subtle/aes_gcm_hkdf.go
@@ -12,7 +12,8 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-// Package subtle provides subtle implementations of the Streaming AEAD primitive.
+// Package subtle provides subtle implementations of the streaming AEAD
+// primitive.
 package subtle
 
 import (
@@ -22,6 +23,7 @@
 	"errors"
 	"fmt"
 	"io"
+	"math"
 
 	subtleaead "github.com/google/tink/go/aead/subtle"
 	"github.com/google/tink/go/subtle/random"
@@ -29,33 +31,53 @@
 )
 
 const (
-	// nonceSizeInBytes is the size of the IVs for GCM.
-	nonceSizeInBytes = 12
+	// AESGCMHKDFNonceSizeInBytes is the size of the IVs for GCM.
+	AESGCMHKDFNonceSizeInBytes = 12
 
-	// NoncePrefixInBytes is the nonce has the format nonce_prefix || ctr || last_block.
+	// AESGCMHKDFNoncePrefixInBytes is the nonce, which has the format:
+	//
+	//   nonce_prefix || ctr || last_block.
+	//
 	// The nonce_prefix is constant for the whole file.
-	// The ctr is a 32 bit ctr, the last_block is 1 if this is the
-	// last block of the file and 0 otherwise.
-	NoncePrefixInBytes = 7
+	//
+	// The ctr is a 32 bit ctr.
+	//
+	// last_block is 1 if this is the last block of the file and 0 otherwise.
+	AESGCMHKDFNoncePrefixInBytes = 7
 
-	// TagSizeInBytes is the size of the tags of each ciphertext segment.
-	TagSizeInBytes = 16
+	// AESGCMHKDFTagSizeInBytes is the size of the tags of each ciphertext
+	// segment.
+	AESGCMHKDFTagSizeInBytes = 16
 )
 
-// AESGCMHKDF implements streaming encryption using AES-GCM with HKDF as key derivation function.
+// AESGCMHKDF implements streaming encryption using AES-GCM with HKDF as the
+// key derivation function.
 //
-// Each ciphertext uses a new AES-GCM key that is derived from the key derivation key, a randomly
-// chosen salt of the same size as the key and a nonce prefix.
+// Each ciphertext uses a new AES-GCM key that is derived from the key
+// derivation key, a randomly chosen salt of the same size as the key and a
+// nonce prefix.
 //
-// The format of a ciphertext is header || segment_0 || segment_1 || ... || segment_k. The
-// header has size HeaderLength(). Its format is headerLength || salt || prefix. where
-// headerLength is 1 byte determining the size of the header, salt is a salt used in the key
-// derivation and prefix is the prefix of the nonce. In principle headerLength is redundant
-// information, since the length of the header can be determined from the key size.
+// The format of a ciphertext is:
 //
-// segment_i is the i-th segment of the ciphertext. The size of segment_1 .. segment_{k-1} is
-// ciphertextSegmentSize. segment_0 is shorter, so that segment_0, the header and other information
-// of size firstSegmentOffset align with ciphertextSegmentSize.
+//   header || segment_0 || segment_1 || ... || segment_k.
+//
+// The format of header is:
+//
+//   headerLength || salt || prefix
+//
+// headerLength is 1 byte determining the size of the header and can be
+// obtained via HeaderLength(). In principle headerLength is redundant
+// information, since the length of the header can be determined from the key
+// size.
+//
+// salt is a salt used in the key derivation.
+//
+// prefix is the prefix of the nonce.
+//
+// segment_i is the i-th segment of the ciphertext. The size of segment_1 ..
+// segment_{k-1} is ciphertextSegmentSize. segment_0 is shorter, so that
+// segment_0, the header and other information of size firstSegmentOffset align
+// with ciphertextSegmentSize.
 type AESGCMHKDF struct {
 	MainKey                      []byte
 	hkdfAlg                      string
@@ -65,14 +87,22 @@
 	plaintextSegmentSize         int
 }
 
-// NewAESGCMHKDF initializes a streaming primitive with a key derivation key and encryption parameters.
+// NewAESGCMHKDF initializes a streaming primitive with a key derivation key
+// and encryption parameters.
 //
-// mainKey argument is an input keying material used to derive sub keys.
-// hkdfAlg argument is a JCE MAC algorithm name, e.g., HmacSha256, used for the HKDF key derivation.
-// keySizeInBytes argument is a key size of the sub keys
+// mainKey is an input keying material used to derive sub keys.
+//
+// hkdfAlg is a JCE MAC algorithm name, e.g., HmacSha256, used for the HKDF key
+// derivation.
+//
+// keySizeInBytes argument is a key size of the sub keys.
+//
 // ciphertextSegmentSize argument is the size of ciphertext segments.
-// firstSegmentOffset argument is the offset of the first ciphertext segment. That means the first
-// segment has size ciphertextSegmentSize - HeaderLength() - firstSegmentOffset
+//
+// firstSegmentOffset argument is the offset of the first ciphertext segment.
+// That means the first segment has size:
+//
+//   ciphertextSegmentSize - HeaderLength() - firstSegmentOffset
 func NewAESGCMHKDF(
 	mainKey []byte,
 	hkdfAlg string,
@@ -86,8 +116,8 @@
 	if err := subtleaead.ValidateAESKeySize(uint32(keySizeInBytes)); err != nil {
 		return nil, err
 	}
-	headerLen := 1 + keySizeInBytes + NoncePrefixInBytes
-	if ciphertextSegmentSize <= firstSegmentOffset+headerLen+TagSizeInBytes {
+	headerLen := 1 + keySizeInBytes + AESGCMHKDFNoncePrefixInBytes
+	if ciphertextSegmentSize <= firstSegmentOffset+headerLen+AESGCMHKDFTagSizeInBytes {
 		return nil, errors.New("ciphertextSegmentSize too small")
 	}
 
@@ -100,25 +130,40 @@
 		keySizeInBytes:               keySizeInBytes,
 		ciphertextSegmentSize:        ciphertextSegmentSize,
 		firstCiphertextSegmentOffset: firstSegmentOffset + headerLen,
-		plaintextSegmentSize:         ciphertextSegmentSize - TagSizeInBytes,
+		plaintextSegmentSize:         ciphertextSegmentSize - AESGCMHKDFTagSizeInBytes,
 	}, nil
 }
 
-// HeaderLength returns a length of the encryption header.
+// HeaderLength returns the length of the encryption header.
 func (a *AESGCMHKDF) HeaderLength() int {
-	return 1 + a.keySizeInBytes + NoncePrefixInBytes
+	return 1 + a.keySizeInBytes + AESGCMHKDFNoncePrefixInBytes
 }
 
-// deriveKey returns a key derived from the given main key using salt and aad parameters.
+// deriveKey returns a key derived from the given main key using salt and aad
+// parameters.
 func (a *AESGCMHKDF) deriveKey(salt, aad []byte) ([]byte, error) {
 	return subtle.ComputeHKDF(a.hkdfAlg, a.MainKey, salt, aad, uint32(a.keySizeInBytes))
 }
 
-// aesGCMHKDFWriter works as a wrapper around underlying io.Writer, which is responsible for
-// encrypting written data. The data is encrypted and flushed in segments of a given size.
-// Once all the data is written aesGCMHKDFWriter must be closed.
+// newCipher creates a new AES-GCM cipher using the given key and the crypto library.
+func (a *AESGCMHKDF) newCipher(key []byte) (cipher.AEAD, error) {
+	aesCipher, err := aes.NewCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	aesGCMCipher, err := cipher.NewGCMWithTagSize(aesCipher, AESGCMHKDFTagSizeInBytes)
+	if err != nil {
+		return nil, err
+	}
+	return aesGCMCipher, nil
+}
+
+// aesGCMHKDFWriter works as a wrapper around underlying io.Writer, which is
+// responsible for encrypting written data. The data is encrypted and flushed
+// in segments of a given size.  Once all the data is written aesGCMHKDFWriter
+// must be closed.
 type aesGCMHKDFWriter struct {
-	encryptedSegments int
+	encryptedSegments uint64
 	noncePrefix       []byte
 	cipher            cipher.AEAD
 	wr                io.Writer
@@ -131,20 +176,21 @@
 	closed bool
 }
 
-// NewEncryptingWriter returns a wrapper around underlying io.Writer, such that any write-operation
-// via the wrapper results in AEAD-encryption of the written data, using aad
-// as associated authenticated data. The associated data is not included in the ciphertext
-// and has to be passed in as parameter for decryption.
+// NewEncryptingWriter returns a wrapper around underlying io.Writer, such that
+// any write-operation via the wrapper results in AEAD-encryption of the
+// written data, using aad as associated authenticated data. The associated
+// data is not included in the ciphertext and has to be passed in as parameter
+// for decryption.
 func (a *AESGCMHKDF) NewEncryptingWriter(w io.Writer, aad []byte) (io.WriteCloser, error) {
 	salt := random.GetRandomBytes(uint32(a.keySizeInBytes))
-	noncePrefix := random.GetRandomBytes(NoncePrefixInBytes)
+	noncePrefix := random.GetRandomBytes(AESGCMHKDFNoncePrefixInBytes)
 
 	dkey, err := a.deriveKey(salt, aad)
 	if err != nil {
 		return nil, err
 	}
 
-	cipher, err := newCipher(dkey)
+	cipher, err := a.newCipher(dkey)
 	if err != nil {
 		return nil, err
 	}
@@ -167,7 +213,8 @@
 	}, nil
 }
 
-// Write encrypts passed data and passes the encrypted data to the underlying writer.
+// Write encrypts passed data and passes the encrypted data to the underlying
+// writer.
 func (w *aesGCMHKDFWriter) Write(p []byte) (int, error) {
 	if w.closed {
 		return 0, errors.New("write on closed writer")
@@ -186,7 +233,10 @@
 			break
 		}
 
-		nonce := generateSegmentNonce(w.noncePrefix, w.encryptedSegments, false)
+		nonce, err := generateAESGCMHKDFSegmentNonce(w.noncePrefix, w.encryptedSegments, false)
+		if err != nil {
+			return 0, err
+		}
 
 		w.ct = w.cipher.Seal(w.ct[0:0], nonce, w.pt[:ptLim], nil)
 		if _, err := w.wr.Write(w.ct); err != nil {
@@ -198,13 +248,17 @@
 	return pos, nil
 }
 
-// Close encrypts the remaining data, flushes it to the underlying writer and closes this writer.
+// Close encrypts the remaining data, flushes it to the underlying writer and
+// closes this writer.
 func (w *aesGCMHKDFWriter) Close() error {
 	if w.closed {
 		return nil
 	}
 
-	nonce := generateSegmentNonce(w.noncePrefix, w.encryptedSegments, true)
+	nonce, err := generateAESGCMHKDFSegmentNonce(w.noncePrefix, w.encryptedSegments, true)
+	if err != nil {
+		return err
+	}
 
 	w.ct = w.cipher.Seal(w.ct[0:0], nonce, w.pt[0:w.ptPos], nil)
 	if _, err := w.wr.Write(w.ct); err != nil {
@@ -218,7 +272,7 @@
 
 // aesGCMHKDFReader works as a wrapper around underlying io.Reader.
 type aesGCMHKDFReader struct {
-	decryptedSegments int
+	decryptedSegments uint64
 	noncePrefix       []byte
 	cipher            cipher.AEAD
 	underlyingReader  io.Reader
@@ -230,9 +284,9 @@
 	firstSegmentOffset int
 }
 
-// NewDecryptingReader returns a wrapper around underlying io.Reader, such that any read-operation
-// via the wrapper results in AEAD-decryption of the underlying ciphertext,
-// using aad as associated authenticated data.
+// NewDecryptingReader returns a wrapper around underlying io.Reader, such that
+// any read-operation via the wrapper results in AEAD-decryption of the
+// underlying ciphertext, using aad as associated authenticated data.
 func (a *AESGCMHKDF) NewDecryptingReader(r io.Reader, aad []byte) (io.Reader, error) {
 	hlen := make([]byte, 1)
 	if _, err := io.ReadFull(r, hlen); err != nil {
@@ -247,7 +301,7 @@
 		return nil, fmt.Errorf("cannot read salt: %v", err)
 	}
 
-	noncePrefix := make([]byte, NoncePrefixInBytes)
+	noncePrefix := make([]byte, AESGCMHKDFNoncePrefixInBytes)
 	if _, err := io.ReadFull(r, noncePrefix); err != nil {
 		return nil, fmt.Errorf("cannot read noncePrefix: %v", err)
 	}
@@ -257,7 +311,7 @@
 		return nil, err
 	}
 
-	cipher, err := newCipher(dkey)
+	cipher, err := a.newCipher(dkey)
 	if err != nil {
 		return nil, err
 	}
@@ -300,7 +354,11 @@
 	} else {
 		segment = r.ctPos + n - 1
 	}
-	nonce := generateSegmentNonce(r.noncePrefix, r.decryptedSegments, lastSegment)
+	nonce, err := generateAESGCMHKDFSegmentNonce(r.noncePrefix, r.decryptedSegments, lastSegment)
+	if err != nil {
+		return 0, nil
+	}
+
 	r.pt, err = r.cipher.Open(r.pt[0:0], nonce, r.ct[:segment], nil)
 	if err != nil {
 		return 0, err
@@ -320,31 +378,21 @@
 	return n, nil
 }
 
-// newCipher creates a new AES-GCM cipher using the given key and the crypto library.
-func newCipher(key []byte) (cipher.AEAD, error) {
-	aesCipher, err := aes.NewCipher(key)
-	if err != nil {
-		return nil, err
-	}
-	ret, err := cipher.NewGCMWithTagSize(aesCipher, TagSizeInBytes)
-	if err != nil {
-		return nil, err
-	}
-	return ret, nil
-}
-
-func generateSegmentNonce(noncePrefix []byte, segmentNr int, last bool) []byte {
+func generateAESGCMHKDFSegmentNonce(noncePrefix []byte, segmentNr uint64, last bool) ([]byte, error) {
 	var l byte
 	if last {
 		l = 1
 	}
 
-	nonce := make([]byte, nonceSizeInBytes)
+	nonce := make([]byte, AESGCMHKDFNonceSizeInBytes)
 	offs := 0
 	copy(nonce, noncePrefix)
 	offs += len(noncePrefix)
+	if segmentNr >= math.MaxUint32 {
+		return nil, errors.New("too many segments")
+	}
 	binary.BigEndian.PutUint32(nonce[offs:], uint32(segmentNr))
 	offs += 4
 	nonce[offs] = l
-	return nonce
+	return nonce, nil
 }
diff --git a/go/subtle/hkdf.go b/go/subtle/hkdf.go
index 41dbec8..cbc1660 100644
--- a/go/subtle/hkdf.go
+++ b/go/subtle/hkdf.go
@@ -27,23 +27,16 @@
 	minTagSizeInBytes = uint32(10)
 )
 
-// Maximum tag size in bytes for each hash type
-var maxTagSizeInBytes = map[string]uint32{
-	"SHA1":   uint32(20),
-	"SHA256": uint32(32),
-	"SHA512": uint32(64),
-}
-
 var errHKDFInvalidInput = errors.New("HKDF: invalid input")
 
 // validateHKDFParams validates parameters of HKDF constructor.
 func validateHKDFParams(hash string, keySize uint32, tagSize uint32) error {
 	// validate tag size
-	maxTagSize, found := maxTagSizeInBytes[hash]
-	if !found {
-		return fmt.Errorf("invalid hash algorithm")
+	digestSize, err := GetHashDigestSize(hash)
+	if err != nil {
+		return err
 	}
-	if tagSize > 255*maxTagSize {
+	if tagSize > 255*digestSize {
 		return fmt.Errorf("tag size too big")
 	}
 	if tagSize < minTagSizeInBytes {
diff --git a/go/subtle/subtle.go b/go/subtle/subtle.go
index 44fa4d0..af6b933 100644
--- a/go/subtle/subtle.go
+++ b/go/subtle/subtle.go
@@ -28,6 +28,23 @@
 
 var errNilHashFunc = errors.New("nil hash function")
 
+// hashDigestSize maps hash algorithms to their digest size in bytes.
+var hashDigestSize = map[string]uint32{
+	"SHA1":   uint32(20),
+	"SHA256": uint32(32),
+	"SHA384": uint32(48),
+	"SHA512": uint32(64),
+}
+
+// GetHashDigestSize returns the digest size of the specified hash algorithm.
+func GetHashDigestSize(hash string) (uint32, error) {
+	digestSize, ok := hashDigestSize[hash]
+	if !ok {
+		return 0, errors.New("invalid hash algorithm")
+	}
+	return digestSize, nil
+}
+
 // ConvertHashName converts different forms of a hash name to the
 // hash name that tink recognizes.
 func ConvertHashName(name string) string {
diff --git a/java_src/BUILD.bazel b/java_src/BUILD.bazel
index 9dd02a3..ecec089 100644
--- a/java_src/BUILD.bazel
+++ b/java_src/BUILD.bazel
@@ -1,8 +1,7 @@
 load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 load("//tools:gen_maven_jar_rules.bzl", "gen_maven_jar_rules")
-load("//tools:jar_jar.bzl", "jar_jar")
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
 
 licenses(["notice"])
 
@@ -201,21 +200,21 @@
 gen_maven_jar_rules(
     name = "tink-android",
     doctitle = "Tink Cryptography API for Android",
+    resources = glob([
+        "src/main/resources/**",
+    ]),
     root_packages = [
         "com.google.crypto.tink",
-        # The following package(s) will be shaded by the "tink-android-shaded" target.
+    ],
+    shaded_packages = [
+        # The following package(s) will be shaded, according to the rules
+        # specified in shading_rules.
         "com.google.protobuf",
     ],
+    shading_rules = "jar_jar_rules.txt",
     deps = lite_proto_deps + android_deps + cleartext_android_deps + subtle_deps,
 )
 
-# Shade embedded packages to avoid dependency version conflicts in user projects.
-jar_jar(
-    name = "tink-android-shaded",
-    input_jar = ":tink-android",
-    rules = "jar_jar_rules.txt",
-)
-
 # TEST
 
 java_library(
diff --git a/java_src/jar_jar_rules.txt b/java_src/jar_jar_rules.txt
index d3c6913..d6a3f01 100644
--- a/java_src/jar_jar_rules.txt
+++ b/java_src/jar_jar_rules.txt
@@ -1,2 +1,6 @@
 # Rules File Format: https://github.com/bazelbuild/bazel/blob/master/third_party/jarjar/java/com/tonicsystems/jarjar/help.txt.
+#
+# Shade protobuf to avoid dependency version conflicts in user projects.
+# WARNING: the shaded package name com.google.crypto.tink.shaded.protobuf must
+# be kept in sync with src/main/resources/META-INF/proguard/tink.pro.
 rule com.google.protobuf.** com.google.crypto.tink.shaded.protobuf.@1
diff --git a/java_src/proto/BUILD.bazel b/java_src/proto/BUILD.bazel
index 0d45b68..1099998 100644
--- a/java_src/proto/BUILD.bazel
+++ b/java_src/proto/BUILD.bazel
@@ -1,4 +1,4 @@
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
 
 licenses(["notice"])
 
@@ -106,6 +106,16 @@
 )
 
 java_proto_library(
+    name = "jws_hmac_java_proto",
+    deps = ["@tink_base//proto:jws_hmac_proto"],
+)
+
+java_lite_proto_library(
+    name = "jws_hmac_java_proto_lite",
+    deps = ["@tink_base//proto:jws_hmac_proto"],
+)
+
+java_proto_library(
     name = "aes_ctr_java_proto",
     deps = ["@tink_base//proto:aes_ctr_proto"],
 )
diff --git a/java_src/src/main/java/com/google/crypto/tink/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/BUILD.bazel
index 8d56e42..d26eb3b 100644
--- a/java_src/src/main/java/com/google/crypto/tink/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/BUILD.bazel
@@ -1,10 +1,562 @@
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 load("@tink_base//:tink_version.bzl", "TINK_VERSION_LABEL")
 load("@tink_base//tools:common.bzl", "template_rule")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+# Primitives
+
+java_library(
+    name = "aead",
+    srcs = ["Aead.java"],
+)
+
+java_library(
+    name = "streaming_aead",
+    srcs = ["StreamingAead.java"],
+)
+
+java_library(
+    name = "deterministic_aead",
+    srcs = ["DeterministicAead.java"],
+)
+
+java_library(
+    name = "hybrid_decrypt",
+    srcs = ["HybridDecrypt.java"],
+)
+
+java_library(
+    name = "hybrid_encrypt",
+    srcs = ["HybridEncrypt.java"],
+)
+
+java_library(
+    name = "mac",
+    srcs = ["Mac.java"],
+)
+
+java_library(
+    name = "key_wrap",
+    srcs = ["KeyWrap.java"],
+)
+
+java_library(
+    name = "public_key_sign",
+    srcs = ["PublicKeySign.java"],
+)
+
+java_library(
+    name = "public_key_verify",
+    srcs = ["PublicKeyVerify.java"],
+)
+
+# Other public interfaces
+
+java_library(
+    name = "crypto_format",
+    srcs = ["CryptoFormat.java"],
+    deps = ["//proto:tink_java_proto"],
+)
+
+android_library(
+    name = "crypto_format-android",
+    srcs = ["CryptoFormat.java"],
+    deps = ["//proto:tink_java_proto_lite"],
+)
+
+java_library(
+    name = "primitive_wrapper",
+    srcs = ["PrimitiveWrapper.java"],
+    deps = [":primitive_set"],
+)
+
+android_library(
+    name = "primitive_wrapper-android",
+    srcs = ["PrimitiveWrapper.java"],
+    deps = [":primitive_set-android"],
+)
+
+java_library(
+    name = "kms_client",
+    srcs = ["KmsClient.java"],
+    deps = [":aead"],
+)
+
+java_library(
+    name = "kms_clients",
+    srcs = ["KmsClients.java"],
+    deps = [":kms_client"],
+)
+
+java_library(
+    name = "keyset_writer",
+    srcs = ["KeysetWriter.java"],
+    deps = ["//proto:tink_java_proto"],
+)
+
+android_library(
+    name = "keyset_writer-android",
+    srcs = ["KeysetWriter.java"],
+    deps = ["//proto:tink_java_proto_lite"],
+)
+
+java_library(
+    name = "binary_keyset_writer",
+    srcs = ["BinaryKeysetWriter.java"],
+    deps = [
+        ":keyset_writer",
+        "//proto:tink_java_proto",
+    ],
+)
+
+android_library(
+    name = "binary_keyset_writer-android",
+    srcs = ["BinaryKeysetWriter.java"],
+    deps = [
+        ":keyset_writer-android",
+        "//proto:tink_java_proto_lite",
+    ],
+)
+
+java_library(
+    name = "json_keyset_writer",
+    srcs = ["JsonKeysetWriter.java"],
+    deps = [
+        ":keyset_writer",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink/subtle:base64",
+        "@maven//:org_json_json",
+    ],
+)
+
+android_library(
+    name = "json_keyset_writer-android",
+    srcs = ["JsonKeysetWriter.java"],
+    deps = [
+        ":keyset_writer",
+        ":keyset_writer-android",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/subtle:base64",
+        "@maven//:org_json_json",
+    ],
+)
+
+java_library(
+    name = "keyset_reader",
+    srcs = ["KeysetReader.java"],
+    deps = ["//proto:tink_java_proto"],
+)
+
+android_library(
+    name = "keyset_reader-android",
+    srcs = ["KeysetReader.java"],
+    deps = ["//proto:tink_java_proto_lite"],
+)
+
+java_library(
+    name = "binary_keyset_reader",
+    srcs = ["BinaryKeysetReader.java"],
+    deps = [
+        ":keyset_reader",
+        "//proto:tink_java_proto",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "binary_keyset_reader-android",
+    srcs = ["BinaryKeysetReader.java"],
+    deps = [
+        ":keyset_reader-android",
+        "//proto:tink_java_proto_lite",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "json_keyset_reader",
+    srcs = ["JsonKeysetReader.java"],
+    deps = [
+        ":keyset_reader",
+        ":util",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink/subtle:base64",
+        "@com_google_protobuf//:protobuf_javalite",
+        "@maven//:org_json_json",
+    ],
+)
+
+android_library(
+    name = "json_keyset_reader-android",
+    srcs = ["JsonKeysetReader.java"],
+    deps = [
+        ":keyset_reader-android",
+        ":util-android",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/subtle:base64",
+        "@com_google_protobuf//:protobuf_javalite",
+        "@maven//:org_json_json",
+    ],
+)
+
+java_library(
+    name = "private_key_manager",
+    srcs = ["PrivateKeyManager.java"],
+    deps = [
+        ":key_manager",
+        "//proto:tink_java_proto",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "private_key_manager-android",
+    srcs = ["PrivateKeyManager.java"],
+    deps = [
+        ":key_manager-android",
+        "//proto:tink_java_proto_lite",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "privileged_registry",
+    srcs = ["PrivilegedRegistry.java"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":registry_cluster",
+        "//proto:tink_java_proto",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "privileged_registry-android",
+    srcs = ["PrivilegedRegistry.java"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":registry_cluster-android",
+        "//proto:tink_java_proto_lite",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "key_type_manager",
+    srcs = ["KeyTypeManager.java"],
+    deps = [
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "key_type_manager-android",
+    srcs = ["KeyTypeManager.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "key_template",
+    srcs = ["KeyTemplate.java"],
+    deps = [
+        "//proto:tink_java_proto",
+        "@com_google_protobuf//:protobuf_javalite",
+        "@maven//:com_google_errorprone_error_prone_annotations",
+    ],
+)
+
+android_library(
+    name = "key_template-android",
+    srcs = ["KeyTemplate.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "@com_google_protobuf//:protobuf_javalite",
+        "@maven//:com_google_errorprone_error_prone_annotations",
+    ],
+)
+
+java_library(
+    name = "config",
+    srcs = ["Config.java"],
+    deps = [
+        ":catalogue",
+        ":key_manager",
+        ":registry_cluster",
+        "//proto:config_java_proto",
+    ],
+)
+
+android_library(
+    name = "config-android",
+    srcs = ["Config.java"],
+    deps = [
+        ":catalogue-android",
+        ":key_manager-android",
+        ":registry_cluster-android",
+        "//proto:config_java_proto_lite",
+    ],
+)
+
+java_library(
+    name = "private_key_manager_impl",
+    srcs = ["PrivateKeyManagerImpl.java"],
+    deps = [
+        ":key_manager_impl",
+        ":key_type_manager",
+        ":private_key_manager",
+        ":private_key_type_manager",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "private_key_manager_impl-android",
+    srcs = ["PrivateKeyManagerImpl.java"],
+    deps = [
+        ":key_manager_impl-android",
+        ":key_type_manager-android",
+        ":private_key_manager-android",
+        ":private_key_type_manager-android",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "no_secret_keyset_handle",
+    srcs = ["NoSecretKeysetHandle.java"],
+    deps = [
+        ":keyset_reader",
+        ":registry_cluster",
+        "//proto:tink_java_proto",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "no_secret_keyset_handle-android",
+    srcs = ["NoSecretKeysetHandle.java"],
+    deps = [
+        ":keyset_reader-android",
+        ":registry_cluster-android",
+        "//proto:tink_java_proto_lite",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "cleartext_keyset_handle",
+    srcs = ["CleartextKeysetHandle.java"],
+    deps = [
+        ":keyset_reader",
+        ":keyset_writer",
+        ":registry_cluster",
+        "//proto:tink_java_proto",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "cleartext_keyset_handle-android",
+    srcs = ["CleartextKeysetHandle.java"],
+    deps = [
+        ":keyset_reader-android",
+        ":keyset_writer-android",
+        ":registry_cluster-android",
+        "//proto:tink_java_proto_lite",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "private_key_type_manager",
+    srcs = ["PrivateKeyTypeManager.java"],
+    deps = [
+        ":key_type_manager",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "private_key_type_manager-android",
+    srcs = ["PrivateKeyTypeManager.java"],
+    deps = [
+        ":key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "util",
+    srcs = ["Util.java"],
+    deps = ["//proto:tink_java_proto"],
+)
+
+android_library(
+    name = "util-android",
+    srcs = ["Util.java"],
+    deps = ["//proto:tink_java_proto_lite"],
+)
+
+java_library(
+    name = "catalogue",
+    srcs = ["Catalogue.java"],
+    deps = [
+        ":key_manager",
+        ":primitive_wrapper",
+    ],
+)
+
+android_library(
+    name = "catalogue-android",
+    srcs = ["Catalogue.java"],
+    deps = [
+        ":key_manager-android",
+        ":primitive_wrapper-android",
+    ],
+)
+
+java_library(
+    name = "key_manager",
+    srcs = ["KeyManager.java"],
+    deps = [
+        "//proto:tink_java_proto",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "key_manager-android",
+    srcs = ["KeyManager.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "key_manager_impl",
+    srcs = ["KeyManagerImpl.java"],
+    deps = [
+        ":key_manager",
+        ":key_type_manager",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "key_manager_impl-android",
+    srcs = ["KeyManagerImpl.java"],
+    deps = [
+        ":key_manager-android",
+        ":key_type_manager-android",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "primitive_set",
+    srcs = ["PrimitiveSet.java"],
+    deps = [
+        ":crypto_format",
+        "//proto:tink_java_proto",
+    ],
+)
+
+android_library(
+    name = "primitive_set-android",
+    srcs = ["PrimitiveSet.java"],
+    deps = [
+        ":crypto_format-android",
+        "//proto:tink_java_proto_lite",
+    ],
+)
+
+java_library(
+    name = "registry_cluster",
+    srcs = [
+        "KeysetHandle.java",
+        "KeysetManager.java",
+        "Registry.java",
+    ],
+    deps = [
+        ":aead",
+        ":catalogue",
+        ":key_manager",
+        ":key_manager_impl",
+        ":key_template",
+        ":key_type_manager",
+        ":keyset_reader",
+        ":keyset_writer",
+        ":primitive_set",
+        ":primitive_wrapper",
+        ":private_key_manager",
+        ":private_key_manager_impl",
+        ":private_key_type_manager",
+        ":util",
+        "//proto:tink_java_proto",
+        "@com_google_protobuf//:protobuf_javalite",
+        "@maven//:com_google_code_findbugs_jsr305",
+    ],
+)
+
+android_library(
+    name = "registry_cluster-android",
+    srcs = [
+        "KeysetHandle.java",
+        "KeysetManager.java",
+        "Registry.java",
+    ],
+    deps = [
+        ":aead",
+        ":catalogue-android",
+        ":key_manager-android",
+        ":key_manager_impl-android",
+        ":key_template-android",
+        ":key_type_manager-android",
+        ":keyset_reader-android",
+        ":keyset_writer-android",
+        ":primitive_set-android",
+        ":primitive_wrapper-android",
+        ":private_key_manager-android",
+        ":private_key_manager_impl-android",
+        ":private_key_type_manager-android",
+        ":util-android",
+        "//proto:tink_java_proto_lite",
+        "@com_google_protobuf//:protobuf_javalite",
+        "@maven//:com_google_code_findbugs_jsr305",
+    ],
+)
+
+template_rule(
+    name = "version_java",
+    src = "Version.java.templ",
+    out = "Version.java",
+    substitutions = {
+        "TINK_VERSION_LABEL": "%s" % TINK_VERSION_LABEL,
+    },
+)
+
+# Deprecated rules, will be removed soon.
 
 full_protos = [
     "//proto:common_java_proto",
@@ -58,15 +610,6 @@
 
 # core, without restricted APIs
 
-template_rule(
-    name = "version_java",
-    src = "Version.java.templ",
-    out = "Version.java",
-    substitutions = {
-        "TINK_VERSION_LABEL": "%s" % TINK_VERSION_LABEL,
-    },
-)
-
 java_library(
     name = "core",
     srcs = glob(
@@ -77,7 +620,7 @@
     deps = full_protos + [
         ":primitives",
         "//src/main/java/com/google/crypto/tink/annotations",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink/subtle:base64",
         "@com_google_protobuf//:protobuf_java",
         "@maven//:com_google_code_findbugs_jsr305",
         "@maven//:com_google_errorprone_error_prone_annotations",
@@ -110,7 +653,7 @@
     deps = lite_protos + [
         ":primitives",
         "//src/main/java/com/google/crypto/tink/annotations",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink/subtle:base64",
         "@com_google_protobuf//:protobuf_javalite",
         "@maven//:com_google_code_findbugs_jsr305",
         "@maven//:com_google_errorprone_error_prone_annotations",
@@ -129,18 +672,6 @@
 )
 
 java_library(
-    name = "cleartext_keyset_handle",
-    srcs = [
-        ":cleartext_keyset_handle_srcs",
-    ],
-    javacopts = JAVACOPTS_OSS,
-    deps = full_protos + [
-        ":tink",
-        "@com_google_protobuf//:protobuf_javalite",
-    ],
-)
-
-java_library(
     name = "cleartext_keyset_handle_android",
     srcs = [
         ":cleartext_keyset_handle_srcs",
@@ -151,27 +682,3 @@
         "@com_google_protobuf//:protobuf_javalite",
     ],
 )
-
-java_library(
-    name = "privileged_registry",
-    srcs = ["PrivilegedRegistry.java"],
-    javacopts = JAVACOPTS_OSS,
-    visibility = ["//visibility:public"],
-    deps = [
-        ":core",
-        "//proto:tink_java_proto",
-        "@com_google_protobuf//:protobuf_javalite",
-    ],
-)
-
-java_library(
-    name = "privileged_registry-android",
-    srcs = ["PrivilegedRegistry.java"],
-    javacopts = JAVACOPTS_OSS,
-    visibility = ["//visibility:public"],
-    deps = [
-        ":core-android",
-        "//proto:tink_java_proto_lite",
-        "@com_google_protobuf//:protobuf_javalite",
-    ],
-)
diff --git a/java_src/src/main/java/com/google/crypto/tink/BinaryKeysetReader.java b/java_src/src/main/java/com/google/crypto/tink/BinaryKeysetReader.java
index 3591f0a..68afc29 100644
--- a/java_src/src/main/java/com/google/crypto/tink/BinaryKeysetReader.java
+++ b/java_src/src/main/java/com/google/crypto/tink/BinaryKeysetReader.java
@@ -33,6 +33,7 @@
  */
 public final class BinaryKeysetReader implements KeysetReader {
   private final InputStream inputStream;
+  private final boolean closeStreamAfterReading;
 
   /**
    * Static method to create a BinaryKeysetReader from an {@link InputStream}.
@@ -41,12 +42,13 @@
    * BinaryKeysetReader#readEncrypted} is called.
    */
   public static KeysetReader withInputStream(InputStream stream) {
-    return new BinaryKeysetReader(stream);
+    return new BinaryKeysetReader(stream, /*closeStreamAfterReading=*/ false);
   }
 
   /** Static method to create a BinaryKeysetReader from a byte arrary. */
   public static KeysetReader withBytes(final byte[] bytes) {
-    return new BinaryKeysetReader(new ByteArrayInputStream(bytes));
+    return new BinaryKeysetReader(
+        new ByteArrayInputStream(bytes), /*closeStreamAfterReading=*/ true);
   }
 
   /**
@@ -56,20 +58,36 @@
    * BinaryKeysetReader#readEncrypted} is called.
    */
   public static KeysetReader withFile(File file) throws IOException {
-    return new BinaryKeysetReader(new FileInputStream(file));
+    return new BinaryKeysetReader(new FileInputStream(file), /*closeStreamAfterReading=*/ true);
   }
 
-  private BinaryKeysetReader(InputStream stream) {
-    inputStream = stream;
+  private BinaryKeysetReader(InputStream stream, boolean closeStreamAfterReading) {
+    this.inputStream = stream;
+    this.closeStreamAfterReading = closeStreamAfterReading;
   }
 
   @Override
   public Keyset read() throws IOException {
-    return Keyset.parseFrom(inputStream, ExtensionRegistryLite.getEmptyRegistry());
+    try {
+      Keyset keyset = Keyset.parseFrom(inputStream, ExtensionRegistryLite.getEmptyRegistry());
+      return keyset;
+    } finally {
+      if (closeStreamAfterReading) {
+        inputStream.close();
+      }
+    }
   }
 
   @Override
   public EncryptedKeyset readEncrypted() throws IOException {
-    return EncryptedKeyset.parseFrom(inputStream, ExtensionRegistryLite.getEmptyRegistry());
+    try {
+      EncryptedKeyset keyset =
+          EncryptedKeyset.parseFrom(inputStream, ExtensionRegistryLite.getEmptyRegistry());
+      return keyset;
+    } finally {
+      if (closeStreamAfterReading) {
+        inputStream.close();
+      }
+    }
   }
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/BinaryKeysetWriter.java b/java_src/src/main/java/com/google/crypto/tink/BinaryKeysetWriter.java
index bd90b12..f7ec632 100644
--- a/java_src/src/main/java/com/google/crypto/tink/BinaryKeysetWriter.java
+++ b/java_src/src/main/java/com/google/crypto/tink/BinaryKeysetWriter.java
@@ -31,28 +31,42 @@
  */
 public final class BinaryKeysetWriter implements KeysetWriter {
   private final OutputStream outputStream;
+  private final boolean closeStreamAfterReading;
 
-  private BinaryKeysetWriter(OutputStream stream) {
-    outputStream = stream;
+  private BinaryKeysetWriter(OutputStream stream, boolean closeStreamAfterReading) {
+    this.outputStream = stream;
+    this.closeStreamAfterReading = closeStreamAfterReading;
   }
 
   /** Static method to create a BinaryKeysetWriter that writes to an {@link OutputStream}. */
   public static KeysetWriter withOutputStream(OutputStream stream) {
-    return new BinaryKeysetWriter(stream);
+    return new BinaryKeysetWriter(stream, /*closeStreamAfterReading=*/ false);
   }
 
   /** Static method to create a BinaryKeysetWriter that writes to a file. */
   public static KeysetWriter withFile(File file) throws IOException {
-    return new BinaryKeysetWriter(new FileOutputStream(file));
+    return new BinaryKeysetWriter(new FileOutputStream(file), /*closeStreamAfterReading=*/ true);
   }
 
   @Override
   public void write(Keyset keyset) throws IOException {
-    outputStream.write(keyset.toByteArray());
+    try {
+      keyset.writeTo(outputStream);
+    } finally {
+      if (closeStreamAfterReading) {
+        outputStream.close();
+      }
+    }
   }
 
   @Override
   public void write(EncryptedKeyset keyset) throws IOException {
-    outputStream.write(keyset.toByteArray());
+    try {
+      keyset.writeTo(outputStream);
+    } finally {
+      if (closeStreamAfterReading) {
+        outputStream.close();
+      }
+    }
   }
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/JsonKeysetReader.java b/java_src/src/main/java/com/google/crypto/tink/JsonKeysetReader.java
index 99e9d36..365ee53 100644
--- a/java_src/src/main/java/com/google/crypto/tink/JsonKeysetReader.java
+++ b/java_src/src/main/java/com/google/crypto/tink/JsonKeysetReader.java
@@ -21,9 +21,7 @@
 import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.proto.KeyStatusType;
 import com.google.crypto.tink.proto.Keyset;
-import com.google.crypto.tink.proto.Keyset.Key;
 import com.google.crypto.tink.proto.KeysetInfo;
-import com.google.crypto.tink.proto.KeysetInfo.KeyInfo;
 import com.google.crypto.tink.proto.OutputPrefixType;
 import com.google.crypto.tink.subtle.Base64;
 import com.google.protobuf.ByteString;
@@ -50,16 +48,19 @@
 
   private final InputStream inputStream;
   private final JSONObject json;
+  private final boolean closeStreamAfterReading;
   private boolean urlSafeBase64 = false;
 
-  private JsonKeysetReader(InputStream inputStream) {
+  private JsonKeysetReader(InputStream inputStream, boolean closeStreamAfterReading) {
     this.inputStream = inputStream;
+    this.closeStreamAfterReading = closeStreamAfterReading;
     json = null;
   }
 
   private JsonKeysetReader(JSONObject json) {
     this.json = json;
     this.inputStream = null;
+    this.closeStreamAfterReading = false;
   }
 
   /**
@@ -69,7 +70,7 @@
    * JsonKeysetReader#readEncrypted} is called.
    */
   public static KeysetReader withInputStream(InputStream input) throws IOException {
-    return new JsonKeysetReader(input);
+    return new JsonKeysetReader(input, /*closeStreamAfterReading=*/ false);
   }
 
   /** Static method to create a JsonKeysetReader from an {@link JSONObject}. */
@@ -79,12 +80,13 @@
 
   /** Static method to create a JsonKeysetReader from a string. */
   public static JsonKeysetReader withString(String input) {
-    return new JsonKeysetReader(new ByteArrayInputStream(input.getBytes(UTF_8)));
+    return new JsonKeysetReader(
+        new ByteArrayInputStream(input.getBytes(UTF_8)), /*closeStreamAfterReading=*/ true);
   }
 
   /** Static method to create a JsonKeysetReader from a byte array. */
   public static JsonKeysetReader withBytes(final byte[] bytes) {
-    return new JsonKeysetReader(new ByteArrayInputStream(bytes));
+    return new JsonKeysetReader(new ByteArrayInputStream(bytes), /*closeStreamAfterReading=*/ true);
   }
 
   /**
@@ -94,7 +96,7 @@
    * JsonKeysetReader#readEncrypted} is called.
    */
   public static JsonKeysetReader withFile(File file) throws IOException {
-    return new JsonKeysetReader(new FileInputStream(file));
+    return new JsonKeysetReader(new FileInputStream(file), /*closeStreamAfterReading=*/ true);
   }
 
   /**
@@ -137,6 +139,10 @@
       }
     } catch (JSONException e) {
       throw new IOException(e);
+    } finally {
+      if (inputStream != null && closeStreamAfterReading) {
+        inputStream.close();
+      }
     }
   }
 
@@ -151,6 +157,10 @@
       }
     } catch (JSONException e) {
       throw new IOException(e);
+    } finally {
+      if (inputStream != null && closeStreamAfterReading) {
+        inputStream.close();
+      }
     }
   }
 
@@ -181,9 +191,9 @@
         .build();
   }
 
-  private Key keyFromJson(JSONObject json) throws JSONException {
+  private Keyset.Key keyFromJson(JSONObject json) throws JSONException {
     validateKey(json);
-    return Key.newBuilder()
+    return Keyset.Key.newBuilder()
         .setStatus(getStatus(json.getString("status")))
         .setKeyId(json.getInt("keyId"))
         .setOutputPrefixType(getOutputPrefixType(json.getString("outputPrefixType")))
@@ -191,7 +201,7 @@
         .build();
   }
 
-  private KeysetInfo keysetInfoFromJson(JSONObject json) throws JSONException {
+  private static KeysetInfo keysetInfoFromJson(JSONObject json) throws JSONException {
     KeysetInfo.Builder builder = KeysetInfo.newBuilder();
     if (json.has("primaryKeyId")) {
       builder.setPrimaryKeyId(json.getInt("primaryKeyId"));
@@ -205,8 +215,8 @@
     return builder.build();
   }
 
-  private KeyInfo keyInfoFromJson(JSONObject json) throws JSONException {
-    return KeyInfo.newBuilder()
+  private static KeysetInfo.KeyInfo keyInfoFromJson(JSONObject json) throws JSONException {
+    return KeysetInfo.KeyInfo.newBuilder()
         .setStatus(getStatus(json.getString("status")))
         .setKeyId(json.getInt("keyId"))
         .setOutputPrefixType(getOutputPrefixType(json.getString("outputPrefixType")))
@@ -229,7 +239,7 @@
         .build();
   }
 
-  private KeyStatusType getStatus(String status) throws JSONException {
+  private static KeyStatusType getStatus(String status) throws JSONException {
     if (status.equals("ENABLED")) {
       return KeyStatusType.ENABLED;
     } else if (status.equals("DISABLED")) {
@@ -238,7 +248,7 @@
     throw new JSONException("unknown status: " + status);
   }
 
-  private OutputPrefixType getOutputPrefixType(String type) throws JSONException {
+  private static OutputPrefixType getOutputPrefixType(String type) throws JSONException {
     if (type.equals("TINK")) {
       return OutputPrefixType.TINK;
     } else if (type.equals("RAW")) {
@@ -251,7 +261,7 @@
     throw new JSONException("unknown output prefix type: " + type);
   }
 
-  private KeyMaterialType getKeyMaterialType(String type) throws JSONException {
+  private static KeyMaterialType getKeyMaterialType(String type) throws JSONException {
     if (type.equals("SYMMETRIC")) {
       return KeyMaterialType.SYMMETRIC;
     } else if (type.equals("ASYMMETRIC_PRIVATE")) {
@@ -264,19 +274,19 @@
     throw new JSONException("unknown key material type: " + type);
   }
 
-  private void validateKeyset(JSONObject json) throws JSONException {
+  private static void validateKeyset(JSONObject json) throws JSONException {
     if (!json.has("key") || json.getJSONArray("key").length() == 0) {
       throw new JSONException("invalid keyset");
     }
   }
 
-  private void validateEncryptedKeyset(JSONObject json) throws JSONException {
+  private static void validateEncryptedKeyset(JSONObject json) throws JSONException {
     if (!json.has("encryptedKeyset")) {
       throw new JSONException("invalid encrypted keyset");
     }
   }
 
-  private void validateKey(JSONObject json) throws JSONException {
+  private static void validateKey(JSONObject json) throws JSONException {
     if (!json.has("keyData")
         || !json.has("status")
         || !json.has("keyId")
@@ -285,7 +295,7 @@
     }
   }
 
-  private void validateKeyData(JSONObject json) throws JSONException {
+  private static void validateKeyData(JSONObject json) throws JSONException {
     if (!json.has("typeUrl") || !json.has("value") || !json.has("keyMaterialType")) {
       throw new JSONException("invalid keyData");
     }
diff --git a/java_src/src/main/java/com/google/crypto/tink/JsonKeysetWriter.java b/java_src/src/main/java/com/google/crypto/tink/JsonKeysetWriter.java
index 30b4ec1..857e20c 100644
--- a/java_src/src/main/java/com/google/crypto/tink/JsonKeysetWriter.java
+++ b/java_src/src/main/java/com/google/crypto/tink/JsonKeysetWriter.java
@@ -19,9 +19,7 @@
 import com.google.crypto.tink.proto.EncryptedKeyset;
 import com.google.crypto.tink.proto.KeyData;
 import com.google.crypto.tink.proto.Keyset;
-import com.google.crypto.tink.proto.Keyset.Key;
 import com.google.crypto.tink.proto.KeysetInfo;
-import com.google.crypto.tink.proto.KeysetInfo.KeyInfo;
 import com.google.crypto.tink.subtle.Base64;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -49,7 +47,11 @@
     outputStream = stream;
   }
 
-  /** Static method to create a JsonKeysetWriter that writes to an {@link OutputStream}. */
+  /**
+   * Static method to create a JsonKeysetWriter that writes to an {@link OutputStream}.
+   *
+   * <p>{@code stream} will be closed after the keyset is written.
+   */
   public static KeysetWriter withOutputStream(OutputStream stream) {
     return new JsonKeysetWriter(stream);
   }
@@ -79,6 +81,8 @@
       outputStream.write(toJson(keyset).toString(4).getBytes(UTF_8));
     } catch (JSONException e) {
       throw new IOException(e);
+    } finally {
+      outputStream.close();
     }
   }
 
@@ -88,9 +92,11 @@
       outputStream.write(toJson(keyset).toString(4).getBytes(UTF_8));
     } catch (JSONException e) {
       throw new IOException(e);
+    } finally {
+      outputStream.close();
     }
   }
-  
+
   private long toUnsignedLong(int x) {
     return ((long) x) & 0xffffffffL;
   }
@@ -99,14 +105,14 @@
     JSONObject json = new JSONObject();
     json.put("primaryKeyId", toUnsignedLong(keyset.getPrimaryKeyId()));
     JSONArray keys = new JSONArray();
-    for (Key key : keyset.getKeyList()) {
+    for (Keyset.Key key : keyset.getKeyList()) {
       keys.put(toJson(key));
     }
     json.put("key", keys);
     return json;
   }
 
-  private JSONObject toJson(Key key) throws JSONException {
+  private JSONObject toJson(Keyset.Key key) throws JSONException {
     return new JSONObject()
         .put("keyData", toJson(key.getKeyData()))
         .put("status", key.getStatus().name())
@@ -131,14 +137,14 @@
     JSONObject json = new JSONObject();
     json.put("primaryKeyId", toUnsignedLong(keysetInfo.getPrimaryKeyId()));
     JSONArray keyInfos = new JSONArray();
-    for (KeyInfo keyInfo : keysetInfo.getKeyInfoList()) {
+    for (KeysetInfo.KeyInfo keyInfo : keysetInfo.getKeyInfoList()) {
       keyInfos.put(toJson(keyInfo));
     }
     json.put("keyInfo", keyInfos);
     return json;
   }
 
-  private JSONObject toJson(KeyInfo keyInfo) throws JSONException {
+  private JSONObject toJson(KeysetInfo.KeyInfo keyInfo) throws JSONException {
     return new JSONObject()
         .put("typeUrl", keyInfo.getTypeUrl())
         .put("status", keyInfo.getStatus().name())
diff --git a/java_src/src/main/java/com/google/crypto/tink/KeyTypeManager.java b/java_src/src/main/java/com/google/crypto/tink/KeyTypeManager.java
index fd7ec38..972b6b0 100644
--- a/java_src/src/main/java/com/google/crypto/tink/KeyTypeManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/KeyTypeManager.java
@@ -79,7 +79,7 @@
    * Constructs a new KeyTypeManager.
    *
    * <p>Takes an arbitrary number of {@link PrimitiveFactory} objects as input. These will be used
-   * and provided via {@link #createPrimitive} to the user.
+   * and provided via {@link #getPrimitive} to the user.
    *
    * @throws IllegalArgumentException if two of the passed in factories produce primitives of the
    *     same class.
diff --git a/java_src/src/main/java/com/google/crypto/tink/Registry.java b/java_src/src/main/java/com/google/crypto/tink/Registry.java
index a761a1b..1864b9b 100644
--- a/java_src/src/main/java/com/google/crypto/tink/Registry.java
+++ b/java_src/src/main/java/com/google/crypto/tink/Registry.java
@@ -26,6 +26,7 @@
 import java.io.InputStream;
 import java.security.GeneralSecurityException;
 import java.util.Collections;
+import java.util.Locale;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
@@ -34,21 +35,21 @@
 /**
  * A global container of key managers and catalogues.
  *
- * <p>Registry maps catalogue names to instances of {@link Catalogue} and each supported key type to
- * a corresponding {@link KeyManager} object, which "understands" the key type (i.e., the KeyManager
- * can instantiate the primitive corresponding to given key, or can generate new keys of the
- * supported key type). It holds also a {@link PrimitiveWrapper} for each supported primitive,
- * so that it can wrap a set of primitives (corresponding to a keyset) into a single primitive.
+ * <p>Registry maps each supported key type to a corresponding {@link KeyManager} object, which
+ * "understands" the key type (i.e., the KeyManager can instantiate the primitive corresponding to
+ * given key, or can generate new keys of the supported key type). It holds also a {@link
+ * PrimitiveWrapper} for each supported primitive, so that it can wrap a set of primitives
+ * (corresponding to a keyset) into a single primitive.
  *
- * <p> Keeping KeyManagers for all primitives in a single Registry (rather than
- * having a separate KeyManager per primitive) enables modular construction of compound primitives
- * from "simple" ones, e.g., AES-CTR-HMAC AEAD encryption uses IND-CPA encryption and a MAC.
+ * <p>Keeping KeyManagers for all primitives in a single Registry (rather than having a separate
+ * KeyManager per primitive) enables modular construction of compound primitives from "simple" ones,
+ * e.g., AES-CTR-HMAC AEAD encryption uses IND-CPA encryption and a MAC.
  *
  * <p>Registry is initialized at startup, and is later used to instantiate primitives for given keys
  * or keysets. Note that regular users will usually not work directly with Registry, but rather via
- * {@link Config} and {@link KeysetHandle#getPrimitive()}-methods, which in the background register
- * and query the Registry for specific KeyManagers and PrimitiveWrappers. Registry is public though,
- * to enable configurations with custom catalogues, primitives or KeyManagers.
+ * {@link TinkConfig} and {@link KeysetHandle#getPrimitive(Class)}-methods, which in the background
+ * register and query the Registry for specific KeyManagers and PrimitiveWrappers. Registry is
+ * public though, to enable configurations with custom catalogues, primitives or KeyManagers.
  *
  * <p>To initialize the Registry with all key managers:
  *
@@ -62,8 +63,7 @@
  * AeadConfig.register();
  * }</pre>
  *
- * <p>After the Registry has been initialized, one can use {@keysetHandle.getPrimitive} to get a
- * primitive. For example, to obtain an {@link Aead} primitive:
+ * <p>After the Registry has been initialized, one can use get a primitive as follows:
  *
  * <pre>{@code
  * KeysetHandle keysetHandle = ...;
@@ -355,8 +355,8 @@
     if (catalogue == null) {
       throw new IllegalArgumentException("catalogue must be non-null.");
     }
-    if (catalogueMap.containsKey(catalogueName.toLowerCase())) {
-      Catalogue<?> existing = catalogueMap.get(catalogueName.toLowerCase());
+    if (catalogueMap.containsKey(catalogueName.toLowerCase(Locale.US))) {
+      Catalogue<?> existing = catalogueMap.get(catalogueName.toLowerCase(Locale.US));
       if (!catalogue.getClass().equals(existing.getClass())) {
         logger.warning(
             "Attempted overwrite of a catalogueName catalogue for name " + catalogueName);
@@ -364,7 +364,7 @@
             "catalogue for name " + catalogueName + " has been already registered");
       }
     }
-    catalogueMap.put(catalogueName.toLowerCase(), catalogue);
+    catalogueMap.put(catalogueName.toLowerCase(Locale.US), catalogue);
   }
 
   /**
@@ -379,25 +379,25 @@
     if (catalogueName == null) {
       throw new IllegalArgumentException("catalogueName must be non-null.");
     }
-    Catalogue<?> catalogue = catalogueMap.get(catalogueName.toLowerCase());
+    Catalogue<?> catalogue = catalogueMap.get(catalogueName.toLowerCase(Locale.US));
     if (catalogue == null) {
       String error = String.format("no catalogue found for %s. ", catalogueName);
-      if (catalogueName.toLowerCase().startsWith("tinkaead")) {
+      if (catalogueName.toLowerCase(Locale.US).startsWith("tinkaead")) {
         error += "Maybe call AeadConfig.register().";
       }
-      if (catalogueName.toLowerCase().startsWith("tinkdeterministicaead")) {
+      if (catalogueName.toLowerCase(Locale.US).startsWith("tinkdeterministicaead")) {
         error += "Maybe call DeterministicAeadConfig.register().";
-      } else if (catalogueName.toLowerCase().startsWith("tinkstreamingaead")) {
+      } else if (catalogueName.toLowerCase(Locale.US).startsWith("tinkstreamingaead")) {
         error += "Maybe call StreamingAeadConfig.register().";
-      } else if (catalogueName.toLowerCase().startsWith("tinkhybriddecrypt")
-          || catalogueName.toLowerCase().startsWith("tinkhybridencrypt")) {
+      } else if (catalogueName.toLowerCase(Locale.US).startsWith("tinkhybriddecrypt")
+          || catalogueName.toLowerCase(Locale.US).startsWith("tinkhybridencrypt")) {
         error += "Maybe call HybridConfig.register().";
-      } else if (catalogueName.toLowerCase().startsWith("tinkmac")) {
+      } else if (catalogueName.toLowerCase(Locale.US).startsWith("tinkmac")) {
         error += "Maybe call MacConfig.register().";
-      } else if (catalogueName.toLowerCase().startsWith("tinkpublickeysign")
-          || catalogueName.toLowerCase().startsWith("tinkpublickeyverify")) {
+      } else if (catalogueName.toLowerCase(Locale.US).startsWith("tinkpublickeysign")
+          || catalogueName.toLowerCase(Locale.US).startsWith("tinkpublickeyverify")) {
         error += "Maybe call SignatureConfig.register().";
-      } else if (catalogueName.toLowerCase().startsWith("tink")) {
+      } else if (catalogueName.toLowerCase(Locale.US).startsWith("tink")) {
         error += "Maybe call TinkConfig.register().";
       }
       throw new GeneralSecurityException(error);
@@ -476,9 +476,7 @@
     }
     String typeUrl = manager.getKeyType();
     ensureKeyManagerInsertable(typeUrl, manager.getClass(), newKeyAllowed);
-    if (!keyManagerMap.containsKey(typeUrl)) {
-      keyManagerMap.put(typeUrl, createContainerFor(manager));
-    }
+    keyManagerMap.putIfAbsent(typeUrl, createContainerFor(manager));
     newKeyAllowedMap.put(typeUrl, Boolean.valueOf(newKeyAllowed));
   }
 
@@ -627,10 +625,9 @@
     if (primitiveWrapperMap.containsKey(classObject)) {
       @SuppressWarnings("unchecked") // We know that we only inserted objects of the correct type.
       PrimitiveWrapper<P> existingWrapper =
-          (PrimitiveWrapper<P>) (primitiveWrapperMap.get(classObject));
+          (PrimitiveWrapper<P>) primitiveWrapperMap.get(classObject);
       if (!wrapper.getClass().equals(existingWrapper.getClass())) {
-        logger.warning(
-            "Attempted overwrite of a registered SetWrapper for type " + classObject.toString());
+        logger.warning("Attempted overwrite of a registered SetWrapper for type " + classObject);
         throw new GeneralSecurityException(
             String.format(
                 "SetWrapper for primitive (%s) is already registered to be %s, "
@@ -758,8 +755,8 @@
   }
 
   /**
-   * Method to derive a key, using the given {@param keyTemplate}, with the randomness as provided
-   * by the second argument.
+   * Method to derive a key, using the given {@code keyTemplate}, with the randomness as provided by
+   * the second argument.
    *
    * <p>This method is on purpose not in the public interface. Calling it twice using different key
    * templates and the same randomness can completely destroy any security in a system, so we
@@ -1051,4 +1048,6 @@
     KeyManagerContainer container = getKeyManagerContainerOrThrow(keyData.getTypeUrl());
     return container.parseKey(keyData.getValue());
   }
+
+  private Registry() {}
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/aead/AeadConfig.java b/java_src/src/main/java/com/google/crypto/tink/aead/AeadConfig.java
index d311175..3e27207 100644
--- a/java_src/src/main/java/com/google/crypto/tink/aead/AeadConfig.java
+++ b/java_src/src/main/java/com/google/crypto/tink/aead/AeadConfig.java
@@ -16,14 +16,14 @@
 
 package com.google.crypto.tink.aead;
 
-import com.google.crypto.tink.Registry;
 import com.google.crypto.tink.mac.MacConfig;
 import com.google.crypto.tink.proto.RegistryConfig;
 import java.security.GeneralSecurityException;
 
 /**
- * Static methods and constants for registering with the {@link Registry} all instances of {@link
- * com.google.crypto.tink.Aead} key types supported in a particular release of Tink.
+ * Static methods and constants for registering with the {@link com.google.crypto.tink.Registry} all
+ * instances of {@link com.google.crypto.tink.Aead} key types supported in a particular release of
+ * Tink.
  *
  * <p>To register all Aead key types provided in the latest Tink version one can do:
  *
@@ -46,18 +46,17 @@
   public static final String XCHACHA20_POLY1305_TYPE_URL =
       new XChaCha20Poly1305KeyManager().getKeyType();
 
-  /** @deprecated */
+  /** @deprecated use {@link #register} */
   @Deprecated public static final RegistryConfig TINK_1_0_0 = RegistryConfig.getDefaultInstance();
 
   /**
-   * @deprecated
+   * @deprecated use {@link #register}
    * @since 1.1.0
    */
   @Deprecated public static final RegistryConfig TINK_1_1_0 = TINK_1_0_0;
 
   /**
-   * * @deprecated
-   *
+   * @deprecated use {@link #register}
    * @since 1.2.0
    */
   @Deprecated public static final RegistryConfig LATEST = TINK_1_0_0;
@@ -71,7 +70,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} and {@link com.google.crypto.tink.KeyManager} needed to
    * handle Aead key types supported in Tink.
    *
@@ -86,7 +85,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} and {@link com.google.crypto.tink.KeyManager} needed to
    * handle Aead key types supported in Tink.
    *
@@ -120,4 +119,6 @@
   public static void registerStandardKeyTypes() throws GeneralSecurityException {
     register();
   }
+
+  private AeadConfig() {}
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManager.java
index eee19c7..316d23c 100644
--- a/java_src/src/main/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/aead/AesCtrHmacAeadKeyManager.java
@@ -79,6 +79,8 @@
   @Override
   public void validateKey(AesCtrHmacAeadKey key) throws GeneralSecurityException {
     Validators.validateVersion(key.getVersion(), getVersion());
+    new AesCtrKeyManager().validateKey(key.getAesCtrKey());
+    new HmacKeyManager().validateKey(key.getHmacKey());
   }
 
   @Override
diff --git a/java_src/src/main/java/com/google/crypto/tink/aead/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/aead/BUILD.bazel
index bee2468..fed96bd 100644
--- a/java_src/src/main/java/com/google/crypto/tink/aead/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/aead/BUILD.bazel
@@ -1,8 +1,443 @@
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "aes_gcm_key_manager",
+    srcs = ["AesGcmKeyManager.java"],
+    deps = [
+        "//proto:aes_gcm_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_gcm_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "aead_key_templates",
+    srcs = ["AeadKeyTemplates.java"],
+    deps = [
+        ":aes_ctr_hmac_aead_key_manager",
+        ":aes_eax_key_manager",
+        ":aes_gcm_key_manager",
+        ":cha_cha20_poly1305_key_manager",
+        ":kms_aead_key_manager",
+        ":kms_envelope_aead_key_manager",
+        ":x_cha_cha20_poly1305_key_manager",
+        "//proto:aes_ctr_hmac_aead_java_proto",
+        "//proto:aes_ctr_java_proto",
+        "//proto:aes_eax_java_proto",
+        "//proto:aes_gcm_java_proto",
+        "//proto:common_java_proto",
+        "//proto:hmac_java_proto",
+        "//proto:kms_aead_java_proto",
+        "//proto:kms_envelope_java_proto",
+        "//proto:tink_java_proto",
+    ],
+)
+
+java_library(
+    name = "aes_ctr_hmac_aead_key_manager",
+    srcs = ["AesCtrHmacAeadKeyManager.java"],
+    deps = [
+        ":aes_ctr_key_manager",
+        "//proto:aes_ctr_hmac_aead_java_proto",
+        "//proto:aes_ctr_java_proto",
+        "//proto:common_java_proto",
+        "//proto:hmac_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/mac:hmac_key_manager",
+        "//src/main/java/com/google/crypto/tink/subtle:encrypt_then_authenticate",
+        "//src/main/java/com/google/crypto/tink/subtle:ind_cpa_cipher",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "aead_factory",
+    srcs = ["AeadFactory.java"],
+    deps = [
+        ":aead_wrapper",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_manager",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+    ],
+)
+
+java_library(
+    name = "kms_aead_key_manager",
+    srcs = ["KmsAeadKeyManager.java"],
+    deps = [
+        "//proto:kms_aead_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:kms_client",
+        "//src/main/java/com/google/crypto/tink:kms_clients",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "kms_envelope_aead",
+    srcs = ["KmsEnvelopeAead.java"],
+    deps = [
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+    ],
+)
+
+java_library(
+    name = "x_cha_cha20_poly1305_key_manager",
+    srcs = ["XChaCha20Poly1305KeyManager.java"],
+    deps = [
+        "//proto:tink_java_proto",
+        "//proto:xchacha20_poly1305_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "//src/main/java/com/google/crypto/tink/subtle:x_cha_cha20_poly1305",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "aead_wrapper",
+    srcs = ["AeadWrapper.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:crypto_format",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+java_library(
+    name = "kms_envelope_aead_key_manager",
+    srcs = ["KmsEnvelopeAeadKeyManager.java"],
+    deps = [
+        ":kms_envelope_aead",
+        "//proto:kms_envelope_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:kms_client",
+        "//src/main/java/com/google/crypto/tink:kms_clients",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "aes_ctr_key_manager",
+    srcs = ["AesCtrKeyManager.java"],
+    deps = [
+        "//proto:aes_ctr_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_ctr_jce_cipher",
+        "//src/main/java/com/google/crypto/tink/subtle:ind_cpa_cipher",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "cha_cha20_poly1305_key_manager",
+    srcs = ["ChaCha20Poly1305KeyManager.java"],
+    deps = [
+        "//proto:chacha20_poly1305_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:cha_cha20_poly1305",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "aes_eax_key_manager",
+    srcs = ["AesEaxKeyManager.java"],
+    deps = [
+        "//proto:aes_eax_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_eax_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "aead_config",
+    srcs = ["AeadConfig.java"],
+    deps = [
+        ":aead_wrapper",
+        ":aes_ctr_hmac_aead_key_manager",
+        ":aes_eax_key_manager",
+        ":aes_gcm_key_manager",
+        ":cha_cha20_poly1305_key_manager",
+        ":kms_aead_key_manager",
+        ":kms_envelope_aead_key_manager",
+        ":x_cha_cha20_poly1305_key_manager",
+        "//proto:config_java_proto",
+        "//src/main/java/com/google/crypto/tink/mac:mac_config",
+    ],
+)
+
+# Android libraries
+
+android_library(
+    name = "aes_gcm_key_manager-android",
+    srcs = ["AesGcmKeyManager.java"],
+    deps = [
+        "//proto:aes_gcm_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_gcm_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "aead_key_templates-android",
+    srcs = ["AeadKeyTemplates.java"],
+    deps = [
+        ":aes_ctr_hmac_aead_key_manager-android",
+        ":aes_eax_key_manager-android",
+        ":aes_gcm_key_manager-android",
+        ":cha_cha20_poly1305_key_manager-android",
+        ":kms_aead_key_manager-android",
+        ":kms_envelope_aead_key_manager-android",
+        ":x_cha_cha20_poly1305_key_manager-android",
+        "//proto:aes_ctr_hmac_aead_java_proto_lite",
+        "//proto:aes_ctr_java_proto_lite",
+        "//proto:aes_eax_java_proto_lite",
+        "//proto:aes_gcm_java_proto_lite",
+        "//proto:common_java_proto_lite",
+        "//proto:hmac_java_proto_lite",
+        "//proto:kms_aead_java_proto_lite",
+        "//proto:kms_envelope_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+    ],
+)
+
+android_library(
+    name = "aes_ctr_hmac_aead_key_manager-android",
+    srcs = ["AesCtrHmacAeadKeyManager.java"],
+    deps = [
+        ":aes_ctr_key_manager-android",
+        "//proto:aes_ctr_hmac_aead_java_proto_lite",
+        "//proto:aes_ctr_java_proto_lite",
+        "//proto:common_java_proto_lite",
+        "//proto:hmac_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/mac:hmac_key_manager-android",
+        "//src/main/java/com/google/crypto/tink/subtle:encrypt_then_authenticate",
+        "//src/main/java/com/google/crypto/tink/subtle:ind_cpa_cipher",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "aead_factory-android",
+    srcs = ["AeadFactory.java"],
+    deps = [
+        ":aead_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_manager-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+    ],
+)
+
+android_library(
+    name = "kms_aead_key_manager-android",
+    srcs = ["KmsAeadKeyManager.java"],
+    deps = [
+        "//proto:kms_aead_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:kms_client",
+        "//src/main/java/com/google/crypto/tink:kms_clients",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "kms_envelope_aead-android",
+    srcs = ["KmsEnvelopeAead.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+    ],
+)
+
+android_library(
+    name = "x_cha_cha20_poly1305_key_manager-android",
+    srcs = ["XChaCha20Poly1305KeyManager.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "//proto:xchacha20_poly1305_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "//src/main/java/com/google/crypto/tink/subtle:x_cha_cha20_poly1305",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "aead_wrapper-android",
+    srcs = ["AeadWrapper.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:crypto_format-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+android_library(
+    name = "kms_envelope_aead_key_manager-android",
+    srcs = ["KmsEnvelopeAeadKeyManager.java"],
+    deps = [
+        ":kms_envelope_aead-android",
+        "//proto:kms_envelope_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:kms_client",
+        "//src/main/java/com/google/crypto/tink:kms_clients",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "aes_ctr_key_manager-android",
+    srcs = ["AesCtrKeyManager.java"],
+    deps = [
+        "//proto:aes_ctr_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_ctr_jce_cipher",
+        "//src/main/java/com/google/crypto/tink/subtle:ind_cpa_cipher",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "cha_cha20_poly1305_key_manager-android",
+    srcs = ["ChaCha20Poly1305KeyManager.java"],
+    deps = [
+        "//proto:chacha20_poly1305_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:cha_cha20_poly1305",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "aes_eax_key_manager-android",
+    srcs = ["AesEaxKeyManager.java"],
+    deps = [
+        "//proto:aes_eax_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_eax_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "aead_config-android",
+    srcs = ["AeadConfig.java"],
+    deps = [
+        ":aead_wrapper-android",
+        ":aes_ctr_hmac_aead_key_manager-android",
+        ":aes_eax_key_manager-android",
+        ":aes_gcm_key_manager-android",
+        ":cha_cha20_poly1305_key_manager-android",
+        ":kms_aead_key_manager-android",
+        ":kms_envelope_aead_key_manager-android",
+        ":x_cha_cha20_poly1305_key_manager-android",
+        "//proto:config_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/mac:mac_config-android",
+    ],
+)
+
+# Deprecated rules, will be removed soon.
 
 filegroup(
     name = "srcs",
diff --git a/java_src/src/main/java/com/google/crypto/tink/aead/subtle/AeadFactory.java b/java_src/src/main/java/com/google/crypto/tink/aead/subtle/AeadFactory.java
new file mode 100644
index 0000000..8b79fbd
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/aead/subtle/AeadFactory.java
@@ -0,0 +1,34 @@
+// 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.aead.subtle;
+
+import com.google.crypto.tink.Aead;
+import com.google.errorprone.annotations.Immutable;
+import java.security.GeneralSecurityException;
+
+/** Provides AEAD instances with a specific raw key. */
+@Immutable
+public interface AeadFactory {
+  /** Returns the size of the AEAD key in bytes. */
+  public int getKeySizeInBytes();
+
+  /**
+   * Creates a new {@code Aead}-primitive that uses the key material given in {@code symmetricKey},
+   * which must be of length {@link #getKeySizeInBytes}.
+   *
+   * @return the newly created {@code Aead}-primitive.
+   */
+  public Aead createAead(final byte[] symmetricKey) throws GeneralSecurityException;
+}
diff --git a/java_src/src/main/java/com/google/crypto/tink/aead/subtle/AesGcmFactory.java b/java_src/src/main/java/com/google/crypto/tink/aead/subtle/AesGcmFactory.java
new file mode 100644
index 0000000..37de892
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/aead/subtle/AesGcmFactory.java
@@ -0,0 +1,56 @@
+// 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.aead.subtle;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.subtle.AesGcmJce;
+import com.google.errorprone.annotations.Immutable;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+
+/** An {@link AeadFactory} that creates new instances of AES-GCM from raw keys */
+@Immutable
+public final class AesGcmFactory implements AeadFactory {
+  private final int keySizeInBytes;
+
+  public AesGcmFactory(int keySizeInBytes) throws GeneralSecurityException {
+    this.keySizeInBytes = validateAesKeySize(keySizeInBytes);
+  }
+
+  @Override
+  public int getKeySizeInBytes() {
+    return keySizeInBytes;
+  }
+
+  @Override
+  public Aead createAead(final byte[] symmetricKey) throws GeneralSecurityException {
+    if (symmetricKey.length != getKeySizeInBytes()) {
+      throw new GeneralSecurityException(
+          String.format(
+              "Symmetric key has incorrect length; expected %s, but got %s",
+              getKeySizeInBytes(), symmetricKey.length));
+    }
+    return new AesGcmJce(symmetricKey);
+  }
+
+  /** @throws InvalidAlgorithmParameterException if {@code sizeInBytes} is not supported. */
+  private static int validateAesKeySize(int sizeInBytes) throws InvalidAlgorithmParameterException {
+    if (sizeInBytes != 16 && sizeInBytes != 32) {
+      throw new InvalidAlgorithmParameterException(
+          String.format("Invalid AES key size, expected 16 or 32, but got %d", sizeInBytes));
+    }
+    return sizeInBytes;
+  }
+}
diff --git a/java_src/src/main/java/com/google/crypto/tink/aead/subtle/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/aead/subtle/BUILD.bazel
new file mode 100644
index 0000000..2f82d99
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/aead/subtle/BUILD.bazel
@@ -0,0 +1,27 @@
+load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+
+licenses(["notice"])
+
+package(default_visibility = ["//:__subpackages__"])
+
+java_library(
+    name = "aead_factory",
+    srcs = ["AeadFactory.java"],
+    javacopts = JAVACOPTS_OSS,
+    deps = [
+        "//src/main/java/com/google/crypto/tink:aead",
+        "@maven//:com_google_errorprone_error_prone_annotations",
+    ],
+)
+
+java_library(
+    name = "aes_gcm_factory",
+    srcs = ["AesGcmFactory.java"],
+    javacopts = JAVACOPTS_OSS,
+    deps = [
+        ":aead_factory",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_gcm_jce",
+        "@maven//:com_google_errorprone_error_prone_annotations",
+    ],
+)
diff --git a/java_src/src/main/java/com/google/crypto/tink/annotations/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/annotations/BUILD.bazel
index eb925f7..44c5b41 100644
--- a/java_src/src/main/java/com/google/crypto/tink/annotations/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/annotations/BUILD.bazel
@@ -2,7 +2,14 @@
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "alpha",
+    srcs = ["Alpha.java"],
+)
+
+# Deprecated rules, will be deleted soon
 
 java_library(
     name = "annotations",
diff --git a/java_src/src/main/java/com/google/crypto/tink/config/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/config/BUILD.bazel
index f145059..3d4f4e2 100644
--- a/java_src/src/main/java/com/google/crypto/tink/config/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/config/BUILD.bazel
@@ -1,8 +1,37 @@
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "tink_config",
+    srcs = ["TinkConfig.java"],
+    deps = [
+        "//proto:config_java_proto",
+        "//src/main/java/com/google/crypto/tink/daead:deterministic_aead_config",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_config",
+        "//src/main/java/com/google/crypto/tink/prf:prf_config",
+        "//src/main/java/com/google/crypto/tink/signature:signature_config",
+        "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_config",
+    ],
+)
+
+android_library(
+    name = "tink_config-android",
+    srcs = ["TinkConfig.java"],
+    deps = [
+        "//proto:config_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/daead:deterministic_aead_config-android",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_config-android",
+        "//src/main/java/com/google/crypto/tink/prf:prf_config-android",
+        "//src/main/java/com/google/crypto/tink/signature:signature_config-android",
+        "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_config-android",
+    ],
+)
+
+# Deprecated rules, will be removed soon
 
 filegroup(
     name = "srcs",
diff --git a/java_src/src/main/java/com/google/crypto/tink/config/TinkConfig.java b/java_src/src/main/java/com/google/crypto/tink/config/TinkConfig.java
index f2552a3..3919f83 100644
--- a/java_src/src/main/java/com/google/crypto/tink/config/TinkConfig.java
+++ b/java_src/src/main/java/com/google/crypto/tink/config/TinkConfig.java
@@ -98,7 +98,9 @@
   public static void register() throws GeneralSecurityException {
     DeterministicAeadConfig.register();
     HybridConfig.register(); // includes Aead and Mac
-    SignatureConfig.register();
     PrfConfig.register();
+    SignatureConfig.register();
+    StreamingAeadConfig.register();
+    // place holder for KeyDerivationConfig. DO NOT EDIT.
   }
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/daead/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/daead/BUILD.bazel
index 8fda110..ea0d75d 100644
--- a/java_src/src/main/java/com/google/crypto/tink/daead/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/daead/BUILD.bazel
@@ -1,8 +1,135 @@
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "deterministic_aead_key_templates",
+    srcs = ["DeterministicAeadKeyTemplates.java"],
+    deps = [
+        ":aes_siv_key_manager",
+        "//proto:aes_siv_java_proto",
+        "//proto:tink_java_proto",
+    ],
+)
+
+android_library(
+    name = "deterministic_aead_key_templates-android",
+    srcs = ["DeterministicAeadKeyTemplates.java"],
+    deps = [
+        ":aes_siv_key_manager-android",
+        "//proto:aes_siv_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+    ],
+)
+
+java_library(
+    name = "deterministic_aead_factory",
+    srcs = ["DeterministicAeadFactory.java"],
+    deps = [
+        ":deterministic_aead_wrapper",
+        "//src/main/java/com/google/crypto/tink:deterministic_aead",
+        "//src/main/java/com/google/crypto/tink:key_manager",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+    ],
+)
+
+android_library(
+    name = "deterministic_aead_factory-android",
+    srcs = ["DeterministicAeadFactory.java"],
+    deps = [
+        ":deterministic_aead_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:deterministic_aead",
+        "//src/main/java/com/google/crypto/tink:key_manager-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+    ],
+)
+
+java_library(
+    name = "deterministic_aead_config",
+    srcs = ["DeterministicAeadConfig.java"],
+    deps = [
+        ":aes_siv_key_manager",
+        ":deterministic_aead_wrapper",
+        "//proto:config_java_proto",
+    ],
+)
+
+android_library(
+    name = "deterministic_aead_config-android",
+    srcs = ["DeterministicAeadConfig.java"],
+    deps = [
+        ":aes_siv_key_manager-android",
+        ":deterministic_aead_wrapper-android",
+        "//proto:config_java_proto_lite",
+    ],
+)
+
+java_library(
+    name = "deterministic_aead_wrapper",
+    srcs = ["DeterministicAeadWrapper.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:crypto_format",
+        "//src/main/java/com/google/crypto/tink:deterministic_aead",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+android_library(
+    name = "deterministic_aead_wrapper-android",
+    srcs = ["DeterministicAeadWrapper.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:crypto_format-android",
+        "//src/main/java/com/google/crypto/tink:deterministic_aead",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+java_library(
+    name = "aes_siv_key_manager",
+    srcs = ["AesSivKeyManager.java"],
+    deps = [
+        "//proto:aes_siv_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:deterministic_aead",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_siv",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "aes_siv_key_manager-android",
+    srcs = ["AesSivKeyManager.java"],
+    deps = [
+        "//proto:aes_siv_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:deterministic_aead",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_siv",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+# Deprecated rules, will be removed soon.
 
 filegroup(
     name = "srcs",
diff --git a/java_src/src/main/java/com/google/crypto/tink/daead/DeterministicAeadConfig.java b/java_src/src/main/java/com/google/crypto/tink/daead/DeterministicAeadConfig.java
index b81a9e1..a6c9e15 100644
--- a/java_src/src/main/java/com/google/crypto/tink/daead/DeterministicAeadConfig.java
+++ b/java_src/src/main/java/com/google/crypto/tink/daead/DeterministicAeadConfig.java
@@ -16,13 +16,13 @@
 
 package com.google.crypto.tink.daead;
 
-import com.google.crypto.tink.Registry;
 import com.google.crypto.tink.proto.RegistryConfig;
 import java.security.GeneralSecurityException;
 
 /**
- * Static methods and constants for registering with the {@link Registry} all instances of {@link
- * com.google.crypto.tink.DeterministicAead} key types supported in a particular release of Tink.
+ * Static methods and constants for registering with the {@link com.google.crypto.tink.Registry} all
+ * instances of {@link com.google.crypto.tink.DeterministicAead} key types supported in a particular
+ * release of Tink.
  *
  * <p>To register all DeterministicAead key types provided in the latest Tink version one can do:
  *
@@ -31,23 +31,21 @@
  * }</pre>
  *
  * <p>For more information on how to obtain and use instances of DeterministicAead, see {@link
- * DeterministicAeadFactory}.
+ * com.google.crypto.tink.KeysetHandle#getPrimitive}.
  *
  * @since 1.1.0
  */
 public final class DeterministicAeadConfig {
   public static final String AES_SIV_TYPE_URL = new AesSivKeyManager().getKeyType();
 
-  /** @deprecated */
-  @Deprecated
-  public static final RegistryConfig TINK_1_1_0 = RegistryConfig.getDefaultInstance();
+  /** @deprecated use {@link #register} */
+  @Deprecated public static final RegistryConfig TINK_1_1_0 = RegistryConfig.getDefaultInstance();
 
   /**
-   * @deprecated
+   * @deprecated use {@link #register}
    * @since 1.2.0
    */
-  @Deprecated
-  public static final RegistryConfig LATEST = RegistryConfig.getDefaultInstance();
+  @Deprecated public static final RegistryConfig LATEST = RegistryConfig.getDefaultInstance();
 
   static {
     try {
@@ -58,7 +56,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} needed to handle DeterministicAead key types supported in
    * Tink.
    *
@@ -73,7 +71,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} needed to handle DeterministicAead key types supported in
    * Tink.
    *
@@ -83,4 +81,6 @@
     AesSivKeyManager.register(/* newKeyAllowed = */ true);
     DeterministicAeadWrapper.register();
   }
+
+  private DeterministicAeadConfig() {}
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel
index 6b23b3f..5c4cd56 100644
--- a/java_src/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/BUILD.bazel
@@ -1,8 +1,339 @@
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "hybrid_decrypt_config",
+    srcs = ["HybridDecryptConfig.java"],
+    deps = [
+        ":hybrid_config",
+        "//src/main/java/com/google/crypto/tink:config",
+    ],
+)
+
+java_library(
+    name = "hybrid_encrypt_wrapper",
+    srcs = ["HybridEncryptWrapper.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+java_library(
+    name = "hybrid_decrypt_wrapper",
+    srcs = ["HybridDecryptWrapper.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:crypto_format",
+        "//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+    ],
+)
+
+java_library(
+    name = "ecies_aead_hkdf_public_key_manager",
+    srcs = ["EciesAeadHkdfPublicKeyManager.java"],
+    deps = [
+        ":hybrid_util",
+        ":registry_ecies_aead_hkdf_dem_helper",
+        "//proto:ecies_aead_hkdf_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_dem_helper",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "registry_ecies_aead_hkdf_dem_helper",
+    srcs = ["RegistryEciesAeadHkdfDemHelper.java"],
+    deps = [
+        "//proto:aes_ctr_hmac_aead_java_proto",
+        "//proto:aes_ctr_java_proto",
+        "//proto:aes_gcm_java_proto",
+        "//proto:hmac_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/aead:aead_config",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_dem_helper",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "hybrid_config",
+    srcs = ["HybridConfig.java"],
+    deps = [
+        ":ecies_aead_hkdf_private_key_manager",
+        ":ecies_aead_hkdf_public_key_manager",
+        ":hybrid_decrypt_wrapper",
+        ":hybrid_encrypt_wrapper",
+        "//proto:config_java_proto",
+        "//src/main/java/com/google/crypto/tink/aead:aead_config",
+    ],
+)
+
+java_library(
+    name = "hybrid_key_templates",
+    srcs = ["HybridKeyTemplates.java"],
+    deps = [
+        ":ecies_aead_hkdf_private_key_manager",
+        "//proto:common_java_proto",
+        "//proto:ecies_aead_hkdf_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink/aead:aead_key_templates",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "ecies_aead_hkdf_private_key_manager",
+    srcs = ["EciesAeadHkdfPrivateKeyManager.java"],
+    deps = [
+        ":ecies_aead_hkdf_public_key_manager",
+        ":hybrid_util",
+        ":registry_ecies_aead_hkdf_dem_helper",
+        "//proto:common_java_proto",
+        "//proto:ecies_aead_hkdf_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/aead:aes_ctr_hmac_aead_key_manager",
+        "//src/main/java/com/google/crypto/tink/aead:aes_gcm_key_manager",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_dem_helper",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "hybrid_util",
+    srcs = ["HybridUtil.java"],
+    deps = [
+        "//proto:common_java_proto",
+        "//proto:ecies_aead_hkdf_java_proto",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+    ],
+)
+
+java_library(
+    name = "hybrid_decrypt_factory",
+    srcs = ["HybridDecryptFactory.java"],
+    deps = [
+        ":hybrid_decrypt_wrapper",
+        "//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink:key_manager",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+    ],
+)
+
+java_library(
+    name = "hybrid_encrypt_factory",
+    srcs = ["HybridEncryptFactory.java"],
+    deps = [
+        ":hybrid_encrypt_wrapper",
+        "//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink:key_manager",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+    ],
+)
+
+java_library(
+    name = "hybrid_encrypt_config",
+    srcs = ["HybridEncryptConfig.java"],
+    deps = [
+        ":hybrid_config",
+        "//src/main/java/com/google/crypto/tink:config",
+    ],
+)
+
+# Android libraries
+
+android_library(
+    name = "hybrid_decrypt_config-android",
+    srcs = ["HybridDecryptConfig.java"],
+    deps = [
+        ":hybrid_config-android",
+        "//src/main/java/com/google/crypto/tink:config-android",
+    ],
+)
+
+android_library(
+    name = "hybrid_encrypt_wrapper-android",
+    srcs = ["HybridEncryptWrapper.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+android_library(
+    name = "hybrid_decrypt_wrapper-android",
+    srcs = ["HybridDecryptWrapper.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:crypto_format-android",
+        "//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+    ],
+)
+
+android_library(
+    name = "ecies_aead_hkdf_public_key_manager-android",
+    srcs = ["EciesAeadHkdfPublicKeyManager.java"],
+    deps = [
+        ":hybrid_util-android",
+        ":registry_ecies_aead_hkdf_dem_helper-android",
+        "//proto:ecies_aead_hkdf_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_dem_helper",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "registry_ecies_aead_hkdf_dem_helper-android",
+    srcs = ["RegistryEciesAeadHkdfDemHelper.java"],
+    deps = [
+        "//proto:aes_ctr_hmac_aead_java_proto_lite",
+        "//proto:aes_ctr_java_proto_lite",
+        "//proto:aes_gcm_java_proto_lite",
+        "//proto:hmac_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/aead:aead_config-android",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_dem_helper",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "hybrid_config-android",
+    srcs = ["HybridConfig.java"],
+    deps = [
+        ":ecies_aead_hkdf_private_key_manager-android",
+        ":ecies_aead_hkdf_public_key_manager-android",
+        ":hybrid_decrypt_wrapper-android",
+        ":hybrid_encrypt_wrapper-android",
+        "//proto:config_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/aead:aead_config-android",
+    ],
+)
+
+android_library(
+    name = "hybrid_key_templates-android",
+    srcs = ["HybridKeyTemplates.java"],
+    deps = [
+        ":ecies_aead_hkdf_private_key_manager-android",
+        "//proto:common_java_proto_lite",
+        "//proto:ecies_aead_hkdf_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/aead:aead_key_templates-android",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "ecies_aead_hkdf_private_key_manager-android",
+    srcs = ["EciesAeadHkdfPrivateKeyManager.java"],
+    deps = [
+        ":ecies_aead_hkdf_public_key_manager-android",
+        ":hybrid_util-android",
+        ":registry_ecies_aead_hkdf_dem_helper-android",
+        "//proto:common_java_proto_lite",
+        "//proto:ecies_aead_hkdf_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/aead:aes_ctr_hmac_aead_key_manager-android",
+        "//src/main/java/com/google/crypto/tink/aead:aes_gcm_key_manager-android",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_dem_helper",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "hybrid_util-android",
+    srcs = ["HybridUtil.java"],
+    deps = [
+        "//proto:common_java_proto_lite",
+        "//proto:ecies_aead_hkdf_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+    ],
+)
+
+android_library(
+    name = "hybrid_decrypt_factory-android",
+    srcs = ["HybridDecryptFactory.java"],
+    deps = [
+        ":hybrid_decrypt_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink:key_manager-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+    ],
+)
+
+android_library(
+    name = "hybrid_encrypt_factory-android",
+    srcs = ["HybridEncryptFactory.java"],
+    deps = [
+        ":hybrid_encrypt_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink:key_manager-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+    ],
+)
+
+android_library(
+    name = "hybrid_encrypt_config-android",
+    srcs = ["HybridEncryptConfig.java"],
+    deps = [
+        ":hybrid_config-android",
+        "//src/main/java/com/google/crypto/tink:config-android",
+    ],
+)
+
+# Deprecate rules, will be removed soon.
 
 filegroup(
     name = "srcs",
diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPrivateKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPrivateKeyManager.java
index 375d8f0..f0a4a4d 100644
--- a/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPrivateKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPrivateKeyManager.java
@@ -18,6 +18,7 @@
 
 import com.google.crypto.tink.HybridDecrypt;
 import com.google.crypto.tink.KeyTemplate;
+import com.google.crypto.tink.KeyTypeManager;
 import com.google.crypto.tink.PrivateKeyTypeManager;
 import com.google.crypto.tink.Registry;
 import com.google.crypto.tink.aead.AesCtrHmacAeadKeyManager;
@@ -55,7 +56,8 @@
     super(
         EciesAeadHkdfPrivateKey.class,
         EciesAeadHkdfPublicKey.class,
-        new PrimitiveFactory<HybridDecrypt, EciesAeadHkdfPrivateKey>(HybridDecrypt.class) {
+        new KeyTypeManager.PrimitiveFactory<HybridDecrypt, EciesAeadHkdfPrivateKey>(
+            HybridDecrypt.class) {
           @Override
           public HybridDecrypt getPrimitive(EciesAeadHkdfPrivateKey recipientKeyProto)
               throws GeneralSecurityException {
diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPublicKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPublicKeyManager.java
index 9975511..9d4f698 100644
--- a/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPublicKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/EciesAeadHkdfPublicKeyManager.java
@@ -40,7 +40,8 @@
   public EciesAeadHkdfPublicKeyManager() {
     super(
         EciesAeadHkdfPublicKey.class,
-        new PrimitiveFactory<HybridEncrypt, EciesAeadHkdfPublicKey>(HybridEncrypt.class) {
+        new KeyTypeManager.PrimitiveFactory<HybridEncrypt, EciesAeadHkdfPublicKey>(
+            HybridEncrypt.class) {
           @Override
           public HybridEncrypt getPrimitive(EciesAeadHkdfPublicKey recipientKeyProto)
               throws GeneralSecurityException {
diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/HybridConfig.java b/java_src/src/main/java/com/google/crypto/tink/hybrid/HybridConfig.java
index fa268d1..9f38f57 100644
--- a/java_src/src/main/java/com/google/crypto/tink/hybrid/HybridConfig.java
+++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/HybridConfig.java
@@ -16,15 +16,14 @@
 
 package com.google.crypto.tink.hybrid;
 
-import com.google.crypto.tink.Registry;
 import com.google.crypto.tink.aead.AeadConfig;
 import com.google.crypto.tink.proto.RegistryConfig;
 import java.security.GeneralSecurityException;
 
 /**
- * Static methods and constants for registering with the {@link Registry} all instances of {@link
- * com.google.crypto.tink.HybridEncrypt} and {@link com.google.crypto.tink.HybridDecrypt} key types
- * supported in a particular release of Tink.
+ * Static methods and constants for registering with the {@link com.google.crypto.tink.Registry} all
+ * instances of {@link com.google.crypto.tink.HybridEncrypt} and {@link
+ * com.google.crypto.tink.HybridDecrypt} key types supported in a particular release of Tink.
  *
  * <p>To register all HybridEncrypt and HybridDecrypt key types provided in the latest Tink version
  * one can do:
@@ -67,7 +66,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} needed to handle HybridDecrypt and HybridEncrypt key types
    * supported in Tink.
    *
@@ -83,7 +82,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} needed to handle HybridDecrypt and HybridEncrypt key types
    * supported in Tink.
    *
diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/BUILD.bazel
new file mode 100644
index 0000000..af6627c
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/BUILD.bazel
@@ -0,0 +1,32 @@
+licenses(["notice"])
+
+package(default_visibility = ["//:__subpackages__"])
+
+java_library(
+    name = "rsa_kem_hybrid_encrypt",
+    srcs = ["RsaKemHybridEncrypt.java"],
+    deps = [
+        ":rsa_kem",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink/aead/subtle:aead_factory",
+        "//src/main/java/com/google/crypto/tink/subtle:hkdf",
+    ],
+)
+
+java_library(
+    name = "rsa_kem",
+    srcs = ["RsaKem.java"],
+)
+
+java_library(
+    name = "rsa_kem_hybrid_decrypt",
+    srcs = ["RsaKemHybridDecrypt.java"],
+    deps = [
+        ":rsa_kem",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink/aead/subtle:aead_factory",
+        "//src/main/java/com/google/crypto/tink/subtle:hkdf",
+    ],
+)
diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/RsaKem.java b/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/RsaKem.java
new file mode 100644
index 0000000..58614db
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/RsaKem.java
@@ -0,0 +1,97 @@
+// 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.hybrid.subtle;
+
+import java.math.BigInteger;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Random;
+
+class RsaKem {
+  static final byte[] EMPTY_AAD = new byte[0];
+  static final int MIN_RSA_KEY_LENGTH_BITS = 2048;
+
+  private RsaKem() {}
+
+  static void validateRsaModulus(BigInteger mod) throws GeneralSecurityException {
+    if (mod.bitLength() < MIN_RSA_KEY_LENGTH_BITS) {
+      throw new GeneralSecurityException(
+          String.format(
+              "RSA key must be of at least size %d bits, but got %d",
+              MIN_RSA_KEY_LENGTH_BITS, mod.bitLength()));
+    }
+  }
+
+  static int bigIntSizeInBytes(BigInteger mod) {
+    return (mod.bitLength() + 7) / 8;
+  }
+
+  /**
+   * Converts {@code bigInt} to a fixed-size byte array, by taking away at most one leading zero
+   * (the sign byte), or adding leading zeros.
+   */
+  static byte[] bigIntToByteArray(BigInteger bigInt, int size) {
+    byte[] value = bigInt.toByteArray();
+    if (value.length == size) {
+      return value;
+    }
+
+    byte[] result = new byte[size];
+    if (value.length == result.length + 1) {
+      if (value[0] != 0) {
+        throw new IllegalArgumentException(
+            "Value is one-byte longer than the expected size, but its first byte is not 0");
+      }
+      System.arraycopy(value, 1, result, 0, result.length);
+    } else if (value.length < result.length) {
+      System.arraycopy(value, 0, result, result.length - value.length, value.length);
+    } else {
+      throw new IllegalArgumentException(
+          String.format(
+              "Value has invalid length, must be of length at most (%d + 1), but" + " got %d",
+              size, value.length));
+    }
+    return result;
+  }
+
+  /**
+   * Generates a random BigInteger in (0, max) (excluding 0 and max) and converts the result to a
+   * byte array having the same length as max.
+   */
+  static byte[] generateSecret(BigInteger max) {
+    int maxSizeInBytes = bigIntSizeInBytes(max);
+    Random rand = new SecureRandom();
+    BigInteger r;
+    do {
+      r = new BigInteger(max.bitLength(), rand);
+    } while (r.signum() <= 0 || r.compareTo(max) >= 0);
+
+    return bigIntToByteArray(r, maxSizeInBytes);
+  }
+
+  static KeyPair generateRsaKeyPair(int keySize) {
+    KeyPairGenerator rsaGenerator;
+    try {
+      rsaGenerator = KeyPairGenerator.getInstance("RSA");
+      rsaGenerator.initialize(keySize);
+    } catch (NoSuchAlgorithmException e) {
+      throw new IllegalStateException("No support for RSA algorithm.", e);
+    }
+    return rsaGenerator.generateKeyPair();
+  }
+}
diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridDecrypt.java b/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridDecrypt.java
new file mode 100644
index 0000000..a2b9285
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridDecrypt.java
@@ -0,0 +1,81 @@
+// 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.hybrid.subtle;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.HybridDecrypt;
+import com.google.crypto.tink.aead.subtle.AeadFactory;
+import com.google.crypto.tink.subtle.Hkdf;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.interfaces.RSAPrivateKey;
+import javax.crypto.Cipher;
+
+/**
+ * Hybrid encryption with RSA-KEM as defined in Shoup's ISO standard proposal as KEM, and AEAD as
+ * DEM and HKDF as KDF.
+ *
+ * <p>Shoup's ISO standard proposal is available at https://www.shoup.net/iso/std6.pdf.
+ */
+public final class RsaKemHybridDecrypt implements HybridDecrypt {
+  private final RSAPrivateKey recipientPrivateKey;
+  private final String hkdfHmacAlgo;
+  private final byte[] hkdfSalt;
+  private final AeadFactory aeadFactory;
+
+  public RsaKemHybridDecrypt(
+      final RSAPrivateKey recipientPrivateKey,
+      String hkdfHmacAlgo,
+      final byte[] hkdfSalt,
+      AeadFactory aeadFactory)
+      throws GeneralSecurityException {
+    RsaKem.validateRsaModulus(recipientPrivateKey.getModulus());
+    this.recipientPrivateKey = recipientPrivateKey;
+    this.hkdfSalt = hkdfSalt;
+    this.hkdfHmacAlgo = hkdfHmacAlgo;
+    this.aeadFactory = aeadFactory;
+  }
+
+  @Override
+  public byte[] decrypt(final byte[] ciphertext, final byte[] contextInfo)
+      throws GeneralSecurityException {
+    int modSizeInBytes = RsaKem.bigIntSizeInBytes(recipientPrivateKey.getModulus());
+    if (ciphertext.length < modSizeInBytes) {
+      throw new GeneralSecurityException(
+          String.format(
+              "Ciphertext must be of at least size %d bytes, but got %d",
+              modSizeInBytes, ciphertext.length));
+    }
+
+    // Decrypt the token to obtain the raw shared secret.
+    ByteBuffer cipherBuffer = ByteBuffer.wrap(ciphertext);
+    byte[] token = new byte[modSizeInBytes];
+    cipherBuffer.get(token);
+    Cipher rsaCipher = Cipher.getInstance("RSA/ECB/NoPadding");
+    rsaCipher.init(Cipher.DECRYPT_MODE, recipientPrivateKey);
+    byte[] sharedSecret = rsaCipher.doFinal(token);
+
+    // KDF: derive a DEM key from the shared secret, salt, and contextInfo.
+    byte[] demKey =
+        Hkdf.computeHkdf(
+            hkdfHmacAlgo, sharedSecret, hkdfSalt, contextInfo, aeadFactory.getKeySizeInBytes());
+
+    // DEM: decrypt the payload.
+    Aead aead = aeadFactory.createAead(demKey);
+    byte[] demPayload = new byte[cipherBuffer.remaining()];
+    cipherBuffer.get(demPayload);
+    return aead.decrypt(demPayload, RsaKem.EMPTY_AAD);
+  }
+}
diff --git a/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridEncrypt.java b/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridEncrypt.java
new file mode 100644
index 0000000..cc7cb27
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridEncrypt.java
@@ -0,0 +1,73 @@
+// 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.hybrid.subtle;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.HybridEncrypt;
+import com.google.crypto.tink.aead.subtle.AeadFactory;
+import com.google.crypto.tink.subtle.Hkdf;
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.interfaces.RSAPublicKey;
+import javax.crypto.Cipher;
+
+/**
+ * Hybrid encryption with RSA-KEM as defined in Shoup's ISO standard proposal as KEM, and AEAD as
+ * DEM and HKDF as KDF.
+ *
+ * <p>Shoup's ISO standard proposal is available at https://www.shoup.net/iso/std6.pdf.
+ */
+public final class RsaKemHybridEncrypt implements HybridEncrypt {
+  private final RSAPublicKey recipientPublicKey;
+  private final String hkdfHmacAlgo;
+  private final byte[] hkdfSalt;
+  private final AeadFactory aeadFactory;
+
+  public RsaKemHybridEncrypt(
+      final RSAPublicKey recipientPublicKey,
+      String hkdfHmacAlgo,
+      final byte[] hkdfSalt,
+      AeadFactory aeadFactory)
+      throws GeneralSecurityException {
+    RsaKem.validateRsaModulus(recipientPublicKey.getModulus());
+    this.recipientPublicKey = recipientPublicKey;
+    this.hkdfHmacAlgo = hkdfHmacAlgo;
+    this.hkdfSalt = hkdfSalt;
+    this.aeadFactory = aeadFactory;
+  }
+
+  @Override
+  public byte[] encrypt(final byte[] plaintext, final byte[] contextInfo)
+      throws GeneralSecurityException {
+    // KEM: generate a random shared secret whose bit length is equal to the modulus'.
+    BigInteger mod = recipientPublicKey.getModulus();
+    byte[] sharedSecret = RsaKem.generateSecret(mod);
+
+    // KEM: encrypt the shared secret using the public key.
+    Cipher rsaCipher = Cipher.getInstance("RSA/ECB/NoPadding");
+    rsaCipher.init(Cipher.ENCRYPT_MODE, recipientPublicKey);
+    byte[] token = rsaCipher.doFinal(sharedSecret);
+
+    // KDF: derive a DEM key from the shared secret, salt, and contextInfo.
+    byte[] demKey =
+        Hkdf.computeHkdf(
+            hkdfHmacAlgo, sharedSecret, hkdfSalt, contextInfo, aeadFactory.getKeySizeInBytes());
+
+    Aead aead = aeadFactory.createAead(demKey);
+    byte[] ciphertext = aead.encrypt(plaintext, RsaKem.EMPTY_AAD);
+    return ByteBuffer.allocate(token.length + ciphertext.length).put(token).put(ciphertext).array();
+  }
+}
diff --git a/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeysetManager.java b/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeysetManager.java
index 8e0da7c..4227d78 100644
--- a/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeysetManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeysetManager.java
@@ -27,12 +27,14 @@
 import com.google.crypto.tink.KeysetReader;
 import com.google.crypto.tink.KeysetWriter;
 import com.google.crypto.tink.proto.OutputPrefixType;
-import com.google.crypto.tink.subtle.Hex;
-import com.google.crypto.tink.subtle.Random;
 import com.google.protobuf.InvalidProtocolBufferException;
+import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.nio.charset.Charset;
 import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.ProviderException;
 import javax.annotation.concurrent.GuardedBy;
 
 /**
@@ -46,11 +48,9 @@
  * <h3>Usage</h3>
  *
  * <pre>{@code
- * String masterKeyUri = "android-keystore://my_master_key_id";
  * AndroidKeysetManager manager = AndroidKeysetManager.Builder()
  *    .withSharedPref(getApplicationContext(), "my_keyset_name", "my_pref_file_name")
  *    .withKeyTemplate(AesGcmHkfStreamingKeyManager.aes128GcmHkdf4KBTemplate())
- *    .withMasterKeyUri(masterKeyUri)
  *    .build();
  * StreamingAead streamingAead = manager.getKeysetHandle().getPrimitive(StreamingAead.class);
  * }</pre>
@@ -59,27 +59,12 @@
  * my_pref_file_name} preferences file. If the preference file name is null, it uses the default
  * preferences file.
  *
- * <p>If the keyset is not found or invalid, and a valid {@link KeyTemplate} is set with {@link
- * AndroidKeysetManager.Builder#withKeyTemplate}, a fresh keyset is generated and is written to the
- * {@code my_keyset_name} preference of the {@code my_pref_file_name} shared preferences file.
+ * <p>If an invalid keyset is found, an {@link InvalidKeyException} is thrown.
  *
- * <p>On Android M or newer and if a master key URI is set with {@link
- * AndroidKeysetManager.Builder#withMasterKeyUri}, the keyset is encrypted with a master key
- * generated and stored in <a
- * href="https://developer.android.com/training/articles/keystore.html">Android Keystore</a>. When
- * Tink cannot decrypt the keyset it would assume that it is not encrypted.
- *
- * <p>The master key URI must start with {@code android-keystore://}. If the master key doesn't
- * exist, a fresh one is generated.
- *
- * <p>Usage of Android Keystore can be disabled with {@link
- * AndroidKeysetManager.Builder#doNotUseKeystore}. Android Keystore on certain devices is broken.
- * Tink runs a self-test to detect such problems and disable Android Keystore accordingly. Users can
- * check whether Android Keystore is in use with {@link #isUsingKeystore}.
- *
- * <p>On Android L or older, or when either Android Keystore is disabled or the master key URI is
- * not set, the keyset will be stored in cleartext in private preferences which, thanks to the
- * security of the Android framework, no other apps can read or write.
+ * <p>If a keyset is not found, and a {@link KeyTemplate} is set with {@link
+ * AndroidKeysetManager.Builder#withKeyTemplate(com.google.crypto.tink.KeyTemplate)}, a fresh keyset
+ * is generated and is written to the {@code my_keyset_name} preference of the {@code
+ * my_pref_file_name} shared preferences file.
  *
  * <p>The resulting manager supports all operations supported by {@link KeysetManager}. For example
  * to rotate the keyset, one can do:
@@ -91,13 +76,41 @@
  * <p>All operations that manipulate the keyset would automatically persist the new keyset to
  * permanent storage.
  *
+ * <h3>Opportunistic keyset encryption with Android Keystore</h3>
+ *
+ * <b>Warning:</b> because Android Keystore is unreliable, we strongly recommend disabling it by not
+ * setting any master key URI.
+ *
+ * <p>If a master key URI is set with {@link AndroidKeysetManager.Builder#withMasterKeyUri}, the
+ * keyset may be encrypted with a key generated and stored in <a
+ * href="https://developer.android.com/training/articles/keystore.html">Android Keystore</a>.
+ *
+ * <p>Android Keystore is only available on Android M or newer. Since it has been found that Android
+ * Keystore is unreliable on certain devices. Tink runs a self-test to detect such problems and
+ * disables Android Keystore accordingly, even if a master key URI is set. Users can check whether
+ * Android Keystore is in use with {@link #isUsingKeystore}.
+ *
+ * <p>When Android Keystore is disabled or otherwise unavailable, keysets will be stored in
+ * cleartext. This is not as bad as it sounds because keysets remain inaccessible to any other apps
+ * running on the same device. Moreover, as of July 2020, most active Android devices support either
+ * full-disk encryption or file-based encryption, which provide strong security protection against
+ * key theft even from attackers with physical access to the device. Android Keystore is only useful
+ * when you want to <a
+ * href="https://developer.android.com/training/articles/keystore#UserAuthentication">require user
+ * authentication for key use</a>, which should be done if and only if you're absolutely sure that
+ * Android Keystore is working properly on your target devices.
+ *
+ * <p>The master key URI must start with {@code android-keystore://}. The remaining of the URI is
+ * used as a key ID when calling Android Keystore. If the master key doesn't exist, a fresh one is
+ * generated. If the master key already exists but is unusable, a {@link KeyStoreException} is
+ * thrown.
+ *
  * @since 1.0.0
  */
 public final class AndroidKeysetManager {
   private static final String TAG = AndroidKeysetManager.class.getSimpleName();
   private final KeysetReader reader;
   private final KeysetWriter writer;
-  private final boolean useKeystore;
   private final Aead masterKey;
   private final KeyTemplate keyTemplate;
 
@@ -106,29 +119,8 @@
 
   private AndroidKeysetManager(Builder builder) throws GeneralSecurityException, IOException {
     reader = builder.reader;
-    if (reader == null) {
-      throw new IllegalArgumentException(
-          "need to specify where to read the keyset from with Builder#withSharedPref");
-    }
-
     writer = builder.writer;
-    if (writer == null) {
-      throw new IllegalArgumentException(
-          "need to specify where to write the keyset to with Builder#withSharedPref");
-    }
-
-    if (builder.useKeystore && builder.masterKeyUri == null) {
-      throw new IllegalArgumentException(
-          "need a master key URI, please set it with Builder#masterKeyUri");
-    }
-    useKeystore = builder.useKeystore && verifyAndroidKeystore();
-
-    if (shouldUseKeystore()) {
-      masterKey = AndroidKeystoreKmsClient.getOrGenerateNewAeadKey(builder.masterKeyUri);
-    } else {
-      masterKey = null;
-    }
-
+    masterKey = builder.masterKey;
     keyTemplate = builder.keyTemplate;
     keysetManager = readOrGenerateNewKeyset();
   }
@@ -138,8 +130,10 @@
     private KeysetReader reader = null;
     private KeysetWriter writer = null;
     private String masterKeyUri = null;
+    private Aead masterKey = null;
     private boolean useKeystore = true;
     private KeyTemplate keyTemplate = null;
+    private KeyStore keyStore = null;
 
     public Builder() {}
 
@@ -168,7 +162,11 @@
         throw new IllegalArgumentException(
             "key URI must start with " + AndroidKeystoreKmsClient.PREFIX);
       }
-      masterKeyUri = val;
+      if (!useKeystore) {
+        throw new IllegalArgumentException(
+            "cannot call withMasterKeyUri() after calling doNotUseKeystore()");
+      }
+      this.masterKeyUri = val;
       return this;
     }
 
@@ -197,14 +195,33 @@
      *
      * <p><b>Warning:</b> When Android Keystore is disabled, keys are stored in cleartext. This
      * should be safe because they are stored in private preferences.
+     *
+     * @deprecated Android Keystore can be disabled by not setting a master key URI.
      */
+    @Deprecated
     public Builder doNotUseKeystore() {
+      masterKeyUri = null;
       useKeystore = false;
       return this;
     }
 
-    /** @return a {@link KeysetHandle} with the specified options. */
+    /** This is for testing only */
+    Builder withKeyStore(KeyStore val) {
+      this.keyStore = val;
+      return this;
+    }
+
+    /**
+     * Builds and returns a new {@link AndroidKeysetManager} with the specified options.
+     *
+     * @throws IOException If a keyset is found but unusable.
+     * @throws KeystoreException If a master key is found but unusable.
+     * @throws GeneralSecurityException If cannot read an existing keyset or generate a new one.
+     */
     public AndroidKeysetManager build() throws GeneralSecurityException, IOException {
+      if (masterKeyUri != null) {
+        masterKey = readOrGenerateNewMasterKey(masterKeyUri, keyStore);
+      }
       return new AndroidKeysetManager(this);
     }
   }
@@ -329,7 +346,7 @@
     return this;
   }
 
-  /** Returns whether this keyset manager is wrapping keys with Android Keystore. */
+  /** Returns whether Android Keystore is being used to wrap Tink keysets. */
   public synchronized boolean isUsingKeystore() {
     return shouldUseKeystore();
   }
@@ -337,9 +354,9 @@
   private KeysetManager readOrGenerateNewKeyset() throws GeneralSecurityException, IOException {
     try {
       return read();
-    } catch (IOException e) {
+    } catch (FileNotFoundException ex) {
       // Not found, handle below.
-      Log.i(TAG, "cannot read keyset: " + e);
+      Log.w(TAG, "keyset not found, will generate a new one", ex);
     }
 
     // Not found.
@@ -350,15 +367,16 @@
       write(manager);
       return manager;
     }
-    throw new GeneralSecurityException("cannot obtain keyset handle");
+    throw new GeneralSecurityException("cannot read or generate keyset");
   }
 
   private KeysetManager read() throws GeneralSecurityException, IOException {
     if (shouldUseKeystore()) {
       try {
         return KeysetManager.withKeysetHandle(KeysetHandle.read(reader, masterKey));
-      } catch (InvalidProtocolBufferException | GeneralSecurityException e) {
-        // This edge case happens when
+      } catch (InvalidProtocolBufferException | GeneralSecurityException ex) {
+        // Swallow the exception and attempt to read the keyset in cleartext.
+        // This edge case may happen when either
         //   - the keyset was generated on a pre M phone which is then upgraded to M or newer, or
         //   - the keyset was generated with Keystore being disabled, then Keystore is enabled.
         // By ignoring the security failure here, an adversary with write access to private
@@ -367,16 +385,11 @@
         // overwrite the encrypted keyset in private preferences of an app, said adversaries must
         // have the same privilege as the app, thus they can call Android Keystore to read or write
         // the encrypted keyset in the first place.
-        // So it's okay to ignore the failure and try to read the keyset in cleartext.
-        Log.i(TAG, "cannot decrypt keyset: " + e);
+        Log.w(TAG, "cannot decrypt keyset: ", ex);
       }
     }
-    KeysetHandle handle = CleartextKeysetHandle.read(reader);
-    if (shouldUseKeystore()) {
-      // Opportunistically encrypt the keyset to avoid further fallback to cleartext.
-      handle.write(writer, masterKey);
-    }
-    return KeysetManager.withKeysetHandle(handle);
+
+    return KeysetManager.withKeysetHandle(CleartextKeysetHandle.read(reader));
   }
 
   private void write(KeysetManager manager) throws GeneralSecurityException {
@@ -392,7 +405,7 @@
   }
 
   private boolean shouldUseKeystore() {
-    return useKeystore && isAtLeastM();
+    return masterKey != null && isAtLeastM();
   }
 
   private static KeyTemplate.OutputPrefixType fromProto(OutputPrefixType outputPrefixType) {
@@ -414,70 +427,46 @@
     return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
   }
 
-  /**
-   * Does a self-test to verify whether we can rely on Android Keystore, which is broken in many
-   * devices.
-   */
-  private static boolean verifyAndroidKeystore() {
+  private static Aead readOrGenerateNewMasterKey(String keyId, KeyStore keyStore)
+      throws GeneralSecurityException {
     if (!isAtLeastM()) {
-      return false;
+      Log.w(TAG, "Android Keystore requires at least Android M");
+      return null;
+    }
+
+    AndroidKeystoreKmsClient client;
+    if (keyStore != null) {
+      client = new AndroidKeystoreKmsClient.Builder().setKeyStore(keyStore).build();
+    } else {
+      client = new AndroidKeystoreKmsClient();
+    }
+
+    boolean existed = client.hasKey(keyId);
+    if (!existed) {
+      try {
+        AndroidKeystoreKmsClient.generateNewAeadKey(keyId);
+      } catch (GeneralSecurityException ex) {
+        Log.w(TAG, "cannot use Android Keystore, it'll be disabled", ex);
+        return null;
+      }
     }
 
     try {
-      String randomKeyId =
-          AndroidKeystoreKmsClient.PREFIX
-              + new String(Random.randBytes(16), Charset.forName("UTF-8"));
-      Aead aead = AndroidKeystoreKmsClient.getOrGenerateNewAeadKey(randomKeyId);
-
-      // Empty message.
-      // Empty aad.
-      byte[] message = new byte[0];
-      byte[] aad = new byte[0];
-      byte[] ciphertext = aead.encrypt(message, aad);
-      byte[] decrypted = aead.decrypt(ciphertext, aad);
-      if (decrypted.length != 0) {
-        Log.i(
-            TAG,
-            "cannot use Android Keystore: encryption/decryption of empty message and empty aad"
-                + " returns incorrect results");
-        return false;
+      return client.getAead(keyId);
+    } catch (GeneralSecurityException | ProviderException ex) {
+      // Throw the exception if the key exists but is unusable. We can't recover by generating a new
+      // key because there might be existing encrypted data under the unusable key.
+      // Users can provide a master key that is stored in StrongBox, which may throw a
+      // ProviderException if there's any problem with it.
+      if (existed) {
+        throw new KeyStoreException(
+            String.format("the master key %s exists but is unusable", keyId), ex);
       }
-
-      // Non-empty message.
-      // Empty aad.
-      message = Random.randBytes(10);
-      aad = new byte[0];
-      ciphertext = aead.encrypt(message, aad);
-      decrypted = aead.decrypt(ciphertext, aad);
-      if (!Hex.encode(decrypted).equals(Hex.encode(message))) {
-        Log.i(
-            TAG,
-            "cannot use Android Keystore: encryption/decryption of non-empty message and empty"
-                + " aad returns incorrect results");
-        return false;
-      }
-
-      // Non-empty message.
-      // Non-empty aad.
-      message = Random.randBytes(10);
-      aad = Random.randBytes(10);
-      ciphertext = aead.encrypt(message, aad);
-      decrypted = aead.decrypt(ciphertext, aad);
-      if (!Hex.encode(decrypted).equals(Hex.encode(message))) {
-        Log.i(
-            TAG,
-            "cannot use Android Keystore: encryption/decryption of non-empty message and"
-                + " non-empty aad returns incorrect results");
-        return false;
-      }
-
-      AndroidKeystoreKmsClient.delete(randomKeyId);
-
-      return true;
-    } catch (Exception ex) {
-      Log.i(TAG, "cannot use Android Keystore: " + ex);
+      // Otherwise swallow the exception if the key doesn't exist yet. We can recover by disabling
+      // Keystore.
+      Log.w(TAG, "cannot use Android Keystore, it'll be disabled", ex);
     }
 
-    return false;
+    return null;
   }
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystoreAesGcm.java b/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystoreAesGcm.java
index 98b1863..d408bb1 100644
--- a/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystoreAesGcm.java
+++ b/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystoreAesGcm.java
@@ -16,8 +16,6 @@
 
 package com.google.crypto.tink.integration.android;
 
-import android.annotation.TargetApi;
-import android.os.Build.VERSION_CODES;
 import com.google.crypto.tink.Aead;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
@@ -35,7 +33,6 @@
  *
  * @since 1.0.0
  */
-@TargetApi(VERSION_CODES.M)
 public final class AndroidKeystoreAesGcm implements Aead {
   // All instances of this class use a 12 byte IV and a 16 byte tag.
   private static final int IV_SIZE_IN_BYTES = 12;
@@ -52,6 +49,14 @@
     }
   }
 
+  /** This is for testing only */
+  AndroidKeystoreAesGcm(String keyId, KeyStore keyStore) throws GeneralSecurityException {
+    key = (SecretKey) keyStore.getKey(keyId, null /* password */);
+    if (key == null) {
+      throw new InvalidKeyException("Keystore cannot load the key with ID: " + keyId);
+    }
+  }
+
   @Override
   public byte[] encrypt(final byte[] plaintext, final byte[] aad)
       throws GeneralSecurityException {
diff --git a/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystoreKmsClient.java b/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystoreKmsClient.java
index a85a22b..31924ba 100644
--- a/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystoreKmsClient.java
+++ b/java_src/src/main/java/com/google/crypto/tink/integration/android/AndroidKeystoreKmsClient.java
@@ -16,17 +16,20 @@
 
 package com.google.crypto.tink.integration.android;
 
-import android.annotation.TargetApi;
 import android.os.Build;
-import android.os.Build.VERSION_CODES;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
+import android.util.Log;
 import com.google.crypto.tink.Aead;
 import com.google.crypto.tink.KmsClient;
+import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.util.Arrays;
+import java.util.Locale;
 import javax.crypto.KeyGenerator;
 
 /**
@@ -37,29 +40,72 @@
  *
  * @since 1.0.0
  */
-@TargetApi(VERSION_CODES.M)
 public final class AndroidKeystoreKmsClient implements KmsClient {
+  private static final String TAG = AndroidKeystoreKmsClient.class.getSimpleName();
+
   /** The prefix of all keys stored in Android Keystore. */
   public static final String PREFIX = "android-keystore://";
 
-  private String keyUri;
+  private final String keyUri;
+  private final KeyStore keyStore;
 
   public AndroidKeystoreKmsClient() throws GeneralSecurityException {
-    if (!isAtLeastM()) {
-      throw new GeneralSecurityException(
-          "needs Android Keystore on Android M or newer");
-    }
+    this(new Builder());
   }
 
   /**
    * Constructs an {@link AndroidKeystoreKmsClient} that is bound to a single key identified by
    * {@code uri}.
+   *
+   * @deprecated use {@link AndroidKeystoreKmsClient#Builder}.
    */
+  @Deprecated
   public AndroidKeystoreKmsClient(String uri) {
-    if (!uri.toLowerCase().startsWith(PREFIX)) {
-      throw new IllegalArgumentException("key URI must starts with " + PREFIX);
+    this(new Builder().setKeyUri(uri));
+  }
+
+  private AndroidKeystoreKmsClient(Builder builder) {
+    this.keyUri = builder.keyUri;
+    this.keyStore = builder.keyStore;
+  }
+
+  /** Builder for AndroidKeystoreKmsClient */
+  public static final class Builder {
+    String keyUri = null;
+    KeyStore keyStore = null;
+
+    public Builder() {
+      if (!isAtLeastM()) {
+        throw new IllegalStateException("need Android Keystore on Android M or newer");
+      }
+
+      try {
+        this.keyStore = KeyStore.getInstance("AndroidKeyStore");
+        this.keyStore.load(null /* param */);
+      } catch (GeneralSecurityException | IOException ex) {
+        throw new IllegalStateException(ex);
+      }
     }
-    this.keyUri = uri;
+
+    public Builder setKeyUri(String val) {
+      if (val == null || !val.toLowerCase(Locale.US).startsWith(PREFIX)) {
+        throw new IllegalArgumentException("val must start with " + PREFIX);
+      }
+      this.keyUri = val;
+      return this;
+    }
+
+    public Builder setKeyStore(KeyStore val) {
+      if (val == null) {
+        throw new IllegalArgumentException("val cannot be null");
+      }
+      this.keyStore = val;
+      return this;
+    }
+
+    public AndroidKeystoreKmsClient build() {
+      return new AndroidKeystoreKmsClient(this);
+    }
   }
 
   /**
@@ -72,7 +118,7 @@
     if (this.keyUri != null && this.keyUri.equals(uri)) {
       return true;
     }
-    return this.keyUri == null && uri.toLowerCase().startsWith(PREFIX);
+    return this.keyUri == null && uri.toLowerCase(Locale.US).startsWith(PREFIX);
   }
 
   /**
@@ -95,10 +141,12 @@
     return new AndroidKeystoreKmsClient();
   }
 
-  private boolean isAtLeastM() {
-    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
-  }
-
+  /**
+   * Returns an {@link Aead} backed by a key in Android Keystore specified by {@code uri}.
+   *
+   * <p>Since Android Keystore is somewhat unreliable, a self-test is done against the key. This
+   * will incur a small performance penalty.
+   */
   @Override
   public Aead getAead(String uri) throws GeneralSecurityException {
     if (this.keyUri != null && !this.keyUri.equals(uri)) {
@@ -106,12 +154,22 @@
           String.format("this client is bound to %s, cannot load keys bound to %s",
               this.keyUri, uri));
     }
-    try {
-      return new AndroidKeystoreAesGcm(
-          Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, uri));
-    } catch (IOException e) {
-      throw new GeneralSecurityException(e);
-    }
+    Aead aead =
+        new AndroidKeystoreAesGcm(
+            Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, uri), keyStore);
+    return validateAead(aead);
+  }
+
+  /** Deletes a key in Android Keystore. */
+  public void deleteKey(String keyUri) throws GeneralSecurityException {
+    String keyId = Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, keyUri);
+    keyStore.deleteEntry(keyId);
+  }
+
+  /** Returns whether a key exists in Android Keystore. */
+  boolean hasKey(String keyUri) throws GeneralSecurityException {
+    String keyId = Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, keyUri);
+    return keyStore.containsAlias(keyId);
   }
 
   /**
@@ -121,13 +179,12 @@
    */
   public static Aead getOrGenerateNewAeadKey(String keyUri)
       throws GeneralSecurityException, IOException {
-    String keyId = Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, keyUri);
-    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
-    keyStore.load(null /* param */);
-    if (!keyStore.containsAlias(keyId)) {
+    AndroidKeystoreKmsClient client = new AndroidKeystoreKmsClient();
+    if (!client.hasKey(keyUri)) {
+      Log.w(TAG, String.format("key URI %s doesn't exist, generating a new one", keyUri));
       generateNewAeadKey(keyUri);
     }
-    return new AndroidKeystoreKmsClient().getAead(keyUri);
+    return client.getAead(keyUri);
   }
 
   /**
@@ -137,6 +194,15 @@
    */
   public static void generateNewAeadKey(String keyUri)
       throws GeneralSecurityException {
+    AndroidKeystoreKmsClient client = new AndroidKeystoreKmsClient();
+    if (client.hasKey(keyUri)) {
+      throw new IllegalArgumentException(
+          String.format(
+              "cannot generate a new key %s because it already exists; please delete it with"
+                  + " deleteKey() and try again",
+              keyUri));
+    }
+
     String keyId = Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, keyUri);
     KeyGenerator keyGenerator = KeyGenerator.getInstance(
         KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
@@ -151,11 +217,23 @@
     keyGenerator.generateKey();
   }
 
-  /** Deletes a key in Android Keystore. */
-  static void delete(String keyUri) throws GeneralSecurityException, IOException {
-    String keyId = Validators.validateKmsKeyUriAndRemovePrefix(PREFIX, keyUri);
-    KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
-    keyStore.load(null /* param */);
-    keyStore.deleteEntry(keyId);
+  /** Does a self-test to verify whether we can rely on Android Keystore */
+  private static Aead validateAead(Aead aead) throws GeneralSecurityException {
+    // Non-empty message and empty aad.
+    // This is a combination that usually fails.
+    byte[] message = Random.randBytes(10);
+    byte[] aad = new byte[0];
+    byte[] ciphertext = aead.encrypt(message, aad);
+    byte[] decrypted = aead.decrypt(ciphertext, aad);
+    if (!Arrays.equals(message, decrypted)) {
+      throw new KeyStoreException(
+          "cannot use Android Keystore: encryption/decryption of non-empty message and empty"
+              + " aad returns an incorrect result");
+    }
+    return aead;
+  }
+
+  private static boolean isAtLeastM() {
+    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
   }
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/integration/android/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/integration/android/BUILD.bazel
index 4908b31..9b16f4d 100644
--- a/java_src/src/main/java/com/google/crypto/tink/integration/android/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/integration/android/BUILD.bazel
@@ -1,42 +1,78 @@
 load("@build_bazel_rules_android//android:rules.bzl", "android_library")
-load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
 
-filegroup(
-    name = "srcs",
-    srcs = glob(
-        [
-            "*.java",
-        ],
-    ),
-)
-
-# public interfaces
-
-java_library(
-    name = "protos",
-    exports = [
-        "//proto:tink_java_proto_lite",
+android_library(
+    name = "android_keystore_kms_client",
+    srcs = ["AndroidKeystoreKmsClient.java"],
+    deps = [
+        ":android_keystore_aes_gcm",
+        "//src/main/java/com/google/crypto/tink:core-android",
+        "//src/main/java/com/google/crypto/tink:primitives",
+        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
     ],
 )
 
 android_library(
-    name = "android",
-    srcs = [
-        ":srcs",
-    ],
-    javacopts = JAVACOPTS_OSS,
-    visibility = ["//visibility:public"],
+    name = "android_keystore_aes_gcm",
+    srcs = ["AndroidKeystoreAesGcm.java"],
     deps = [
-        ":protos",
-        "//src/main/java/com/google/crypto/tink:android",
-        "//src/main/java/com/google/crypto/tink:cleartext_keyset_handle_android",
+        "//src/main/java/com/google/crypto/tink:primitives",
+    ],
+)
+
+android_library(
+    name = "shared_pref_keyset_writer",
+    srcs = ["SharedPrefKeysetWriter.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:core-android",
         "//src/main/java/com/google/crypto/tink/subtle",
-        "//src/main/java/com/google/crypto/tink/subtle:aead",
+    ],
+)
+
+android_library(
+    name = "shared_pref_keyset_reader",
+    srcs = ["SharedPrefKeysetReader.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:core-android",
+        "//src/main/java/com/google/crypto/tink/subtle",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "android_keyset_manager",
+    srcs = ["AndroidKeysetManager.java"],
+    deps = [
+        ":android_keystore_kms_client",
+        ":shared_pref_keyset_reader",
+        ":shared_pref_keyset_writer",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:cleartext_keyset_handle-android",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:keyset_reader-android",
+        "//src/main/java/com/google/crypto/tink:keyset_writer-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
         "@com_google_protobuf//:protobuf_javalite",
         "@maven//:com_google_code_findbugs_jsr305",
     ],
 )
+
+# Deprecated, will be removed soon.
+
+android_library(
+    name = "android",
+    exports = [
+        ":android_keyset_manager",
+        ":android_keystore_aes_gcm",
+        ":android_keystore_kms_client",
+        ":shared_pref_keyset_reader",
+        ":shared_pref_keyset_writer",
+    ],
+)
diff --git a/java_src/src/main/java/com/google/crypto/tink/integration/android/SharedPrefKeysetReader.java b/java_src/src/main/java/com/google/crypto/tink/integration/android/SharedPrefKeysetReader.java
index 8515ff2..93b7425 100644
--- a/java_src/src/main/java/com/google/crypto/tink/integration/android/SharedPrefKeysetReader.java
+++ b/java_src/src/main/java/com/google/crypto/tink/integration/android/SharedPrefKeysetReader.java
@@ -24,6 +24,8 @@
 import com.google.crypto.tink.proto.Keyset;
 import com.google.crypto.tink.subtle.Hex;
 import com.google.protobuf.ExtensionRegistryLite;
+import java.io.CharConversionException;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 
 /**
@@ -60,19 +62,20 @@
     }
   }
 
+  @SuppressWarnings("UnusedException")
   private byte[] readPref() throws IOException {
     try {
       String keysetHex = sharedPreferences.getString(keysetName, null /* default value */);
       if (keysetHex == null) {
-        throw new IOException(
+        throw new FileNotFoundException(
             String.format("can't read keyset; the pref value %s does not exist", keysetName));
       }
       return Hex.decode(keysetHex);
-    } catch (ClassCastException | IllegalArgumentException e) {
-      throw new IOException(
+    } catch (ClassCastException | IllegalArgumentException ex) {
+      // The original exception is swallowed to prevent leaked key material.
+      throw new CharConversionException(
           String.format(
-              "can't read keyset; the pref value %s is not a valid hex string", keysetName),
-          e);
+              "can't read keyset; the pref value %s is not a valid hex string", keysetName));
     }
   }
 
diff --git a/java_src/src/main/java/com/google/crypto/tink/integration/awskms/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/integration/awskms/BUILD.bazel
index 1f8a2d8..ca8600c 100644
--- a/java_src/src/main/java/com/google/crypto/tink/integration/awskms/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/integration/awskms/BUILD.bazel
@@ -1,32 +1,28 @@
-load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
-
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
-
-filegroup(
-    name = "srcs",
-    srcs = glob(
-        [
-            "*.java",
-        ],
-    ),
-)
-
-# public interfaces
+package(default_visibility = ["//visibility:public"])
 
 java_library(
-    name = "awskms",
-    srcs = [
-        ":srcs",
+    name = "aws_kms_aead",
+    srcs = ["AwsKmsAead.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:aead",
+        "@maven//:com_amazonaws_aws_java_sdk_core",
+        "@maven//:com_amazonaws_aws_java_sdk_kms",
     ],
-    javacopts = JAVACOPTS_OSS,
+)
+
+java_library(
+    name = "aws_kms_client",
+    srcs = ["AwsKmsClient.java"],
     plugins = [
         ":auto_service_plugin",
     ],
     deps = [
-        "//src/main/java/com/google/crypto/tink",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        ":aws_kms_aead",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:kms_client",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
         "@maven//:com_amazonaws_aws_java_sdk_core",
         "@maven//:com_amazonaws_aws_java_sdk_kms",
         "@maven//:com_google_auto_service_auto_service_annotations",
@@ -42,3 +38,13 @@
         "@maven//:com_google_auto_service_auto_service",
     ],
 )
+
+# Deprecated rules, will be removed soon.
+
+java_library(
+    name = "awskms",
+    exports = [
+        ":aws_kms_aead",
+        ":aws_kms_client",
+    ],
+)
diff --git a/java_src/src/main/java/com/google/crypto/tink/integration/gcpkms/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/integration/gcpkms/BUILD.bazel
index 7c00c2b..a5628d1 100644
--- a/java_src/src/main/java/com/google/crypto/tink/integration/gcpkms/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/integration/gcpkms/BUILD.bazel
@@ -1,32 +1,19 @@
-load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
-
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
-
-filegroup(
-    name = "srcs",
-    srcs = glob(
-        [
-            "*.java",
-        ],
-    ),
-)
-
-# public interfaces
+package(default_visibility = ["//visibility:public"])
 
 java_library(
-    name = "gcpkms",
-    srcs = [
-        ":srcs",
-    ],
-    javacopts = JAVACOPTS_OSS,
+    name = "gcp_kms_client",
+    srcs = ["GcpKmsClient.java"],
     plugins = [
         ":auto_service_plugin",
     ],
     deps = [
-        "//src/main/java/com/google/crypto/tink",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        ":gcp_kms_aead",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:core",
+        "//src/main/java/com/google/crypto/tink:kms_client",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
         "@maven//:com_google_api_client_google_api_client",
         "@maven//:com_google_apis_google_api_services_cloudkms",
         "@maven//:com_google_auto_service_auto_service_annotations",
@@ -36,6 +23,15 @@
     ],
 )
 
+java_library(
+    name = "gcp_kms_aead",
+    srcs = ["GcpKmsAead.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:aead",
+        "@maven//:com_google_apis_google_api_services_cloudkms",
+    ],
+)
+
 java_plugin(
     name = "auto_service_plugin",
     processor_class = "com.google.auto.service.processor.AutoServiceProcessor",
@@ -44,3 +40,14 @@
         "@maven//:com_google_auto_service_auto_service",
     ],
 )
+
+# Deprecated rules, will be removed soon.
+
+java_library(
+    name = "gcpkms",
+    tags = ["avoid_dep"],
+    exports = [
+        ":gcp_kms_aead",
+        ":gcp_kms_client",
+    ],
+)
diff --git a/java_src/src/main/java/com/google/crypto/tink/jose/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/jose/BUILD.bazel
new file mode 100644
index 0000000..f396045
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/jose/BUILD.bazel
@@ -0,0 +1,17 @@
+licenses(["notice"])
+
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "jws_mac",
+    srcs = ["JwsMac.java"],
+    deps = [
+        ":jwt",
+        "@maven//:com_google_errorprone_error_prone_annotations",
+    ],
+)
+
+java_library(
+    name = "jwt",
+    srcs = ["Jwt.java"],
+)
diff --git a/java_src/src/main/java/com/google/crypto/tink/jose/JwsMac.java b/java_src/src/main/java/com/google/crypto/tink/jose/JwsMac.java
new file mode 100644
index 0000000..840d044
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/jose/JwsMac.java
@@ -0,0 +1,33 @@
+// 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.jose;
+
+import com.google.errorprone.annotations.Immutable;
+import java.security.GeneralSecurityException;
+
+/**
+ * Interface for JSON Web Signature (JWS) Message Authentication Code (MAC), as described in RFC
+ * 7515.
+ *
+ * <h3>Security guarantees: similar to {@link com.google.crypto.tink.Mac}.</h3>
+ */
+@Immutable
+public interface JwsMac {
+  /** Computes a MAC and encodes a JWT in JWS compact serialization format. */
+  String computeMacThenEncode(Jwt payload) throws GeneralSecurityException;
+
+  /** Verifies a MAC and decodes a JWT in JWS compact serialization format. */
+  Jwt verifyMacThenDecode(String compact) throws GeneralSecurityException;
+}
diff --git a/javascript/keyset_writer.js b/java_src/src/main/java/com/google/crypto/tink/jose/Jwt.java
similarity index 60%
copy from javascript/keyset_writer.js
copy to java_src/src/main/java/com/google/crypto/tink/jose/Jwt.java
index cc715df..5237e5a 100644
--- a/javascript/keyset_writer.js
+++ b/java_src/src/main/java/com/google/crypto/tink/jose/Jwt.java
@@ -12,22 +12,9 @@
 //
 ////////////////////////////////////////////////////////////////////////////////
 
-goog.module('tink.KeysetWriter');
+package com.google.crypto.tink.jose;
 
-const {PbEncryptedKeyset, PbKeyset} = goog.require('google3.third_party.tink.javascript.internal.proto');
-
-/**
- * KeysetWriter knows how to write a keyset or an encrypted keyset to some
- * storage system.
- *
- * @record
- */
-class KeysetWriter {
-  /**
-   * @param {!PbKeyset|!PbEncryptedKeyset} keyset
-   * @return {!Uint8Array}
-   */
-  write(keyset) {}
+/** A JWT is a string representing a set of claims as a JSON object, as described in RFC 7519. */
+public final class Jwt {
+  private Jwt() {}
 }
-
-exports = KeysetWriter;
diff --git a/apps/jose/README.md b/java_src/src/main/java/com/google/crypto/tink/jose/README.md
similarity index 100%
rename from apps/jose/README.md
rename to java_src/src/main/java/com/google/crypto/tink/jose/README.md
diff --git a/java_src/src/main/java/com/google/crypto/tink/mac/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/mac/BUILD.bazel
index 199e597..719c12b 100644
--- a/java_src/src/main/java/com/google/crypto/tink/mac/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/mac/BUILD.bazel
@@ -1,8 +1,183 @@
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "hmac_key_manager",
+    srcs = ["HmacKeyManager.java"],
+    deps = [
+        "//proto:common_java_proto",
+        "//proto:hmac_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:prf_hmac_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:prf_mac",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "hmac_key_manager-android",
+    srcs = ["HmacKeyManager.java"],
+    deps = [
+        "//proto:common_java_proto_lite",
+        "//proto:hmac_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:prf_hmac_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:prf_mac",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "mac_factory",
+    srcs = ["MacFactory.java"],
+    deps = [
+        ":mac_wrapper",
+        "//src/main/java/com/google/crypto/tink:key_manager",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+    ],
+)
+
+android_library(
+    name = "mac_factory-android",
+    srcs = ["MacFactory.java"],
+    deps = [
+        ":mac_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:key_manager-android",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+    ],
+)
+
+java_library(
+    name = "mac_key_templates",
+    srcs = ["MacKeyTemplates.java"],
+    deps = [
+        ":aes_cmac_key_manager",
+        ":hmac_key_manager",
+        "//proto:aes_cmac_java_proto",
+        "//proto:common_java_proto",
+        "//proto:hmac_java_proto",
+        "//proto:tink_java_proto",
+    ],
+)
+
+android_library(
+    name = "mac_key_templates-android",
+    srcs = ["MacKeyTemplates.java"],
+    deps = [
+        ":aes_cmac_key_manager-android",
+        ":hmac_key_manager-android",
+        "//proto:aes_cmac_java_proto_lite",
+        "//proto:common_java_proto_lite",
+        "//proto:hmac_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+    ],
+)
+
+java_library(
+    name = "mac_wrapper",
+    srcs = ["MacWrapper.java"],
+    deps = [
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:crypto_format",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+android_library(
+    name = "mac_wrapper-android",
+    srcs = ["MacWrapper.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:crypto_format-android",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+java_library(
+    name = "mac_config",
+    srcs = ["MacConfig.java"],
+    deps = [
+        ":aes_cmac_key_manager",
+        ":hmac_key_manager",
+        ":mac_wrapper",
+        "//proto:config_java_proto",
+    ],
+)
+
+android_library(
+    name = "mac_config-android",
+    srcs = ["MacConfig.java"],
+    deps = [
+        ":aes_cmac_key_manager-android",
+        ":hmac_key_manager-android",
+        ":mac_wrapper-android",
+        "//proto:config_java_proto_lite",
+    ],
+)
+
+java_library(
+    name = "aes_cmac_key_manager",
+    srcs = ["AesCmacKeyManager.java"],
+    deps = [
+        "//proto:aes_cmac_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_cmac",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "aes_cmac_key_manager-android",
+    srcs = ["AesCmacKeyManager.java"],
+    deps = [
+        "//proto:aes_cmac_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_cmac",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+# Deprecated rules, will be removed soon
 
 filegroup(
     name = "srcs",
diff --git a/java_src/src/main/java/com/google/crypto/tink/mac/HmacKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/mac/HmacKeyManager.java
index 703fc11..11e0691 100644
--- a/java_src/src/main/java/com/google/crypto/tink/mac/HmacKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/mac/HmacKeyManager.java
@@ -25,7 +25,8 @@
 import com.google.crypto.tink.proto.HmacKeyFormat;
 import com.google.crypto.tink.proto.HmacParams;
 import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
-import com.google.crypto.tink.subtle.MacJce;
+import com.google.crypto.tink.subtle.PrfHmacJce;
+import com.google.crypto.tink.subtle.PrfMac;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.subtle.Validators;
 import com.google.protobuf.ByteString;
@@ -37,7 +38,8 @@
 import javax.crypto.spec.SecretKeySpec;
 
 /**
- * This key manager generates new {@code HmacKey} keys and produces new instances of {@code MacJce}.
+ * This key manager generates new {@code HmacKey} keys and produces new instances of {@code
+ * PrfHmacJce}.
  */
 public final class HmacKeyManager extends KeyTypeManager<HmacKey> {
   public HmacKeyManager() {
@@ -52,11 +54,11 @@
             int tagSize = key.getParams().getTagSize();
             switch (hash) {
               case SHA1:
-                return new MacJce("HMACSHA1", keySpec, tagSize);
+                return new PrfMac(new PrfHmacJce("HMACSHA1", keySpec), tagSize);
               case SHA256:
-                return new MacJce("HMACSHA256", keySpec, tagSize);
+                return new PrfMac(new PrfHmacJce("HMACSHA256", keySpec), tagSize);
               case SHA512:
-                return new MacJce("HMACSHA512", keySpec, tagSize);
+                return new PrfMac(new PrfHmacJce("HMACSHA512", keySpec), tagSize);
               default:
                 throw new GeneralSecurityException("unknown hash");
             }
diff --git a/java_src/src/main/java/com/google/crypto/tink/mac/MacConfig.java b/java_src/src/main/java/com/google/crypto/tink/mac/MacConfig.java
index 802a70a..7c202c2 100644
--- a/java_src/src/main/java/com/google/crypto/tink/mac/MacConfig.java
+++ b/java_src/src/main/java/com/google/crypto/tink/mac/MacConfig.java
@@ -16,13 +16,13 @@
 
 package com.google.crypto.tink.mac;
 
-import com.google.crypto.tink.Registry;
 import com.google.crypto.tink.proto.RegistryConfig;
 import java.security.GeneralSecurityException;
 
 /**
- * Static methods and constants for registering with the {@link Registry} all instances of {@link
- * com.google.crypto.tink.Mac} key types supported in a particular release of Tink.
+ * Static methods and constants for registering with the {@link com.google.crypto.tink.Registry} all
+ * instances of {@link com.google.crypto.tink.Mac} key types supported in a particular release of
+ * Tink.
  *
  * <p>To register all Mac key types provided in the latest Tink version one can do:
  *
@@ -30,30 +30,28 @@
  * MacConfig.register();
  * }</pre>
  *
- * <p>For more information on how to obtain and use instances of Mac, see {@link MacFactory}.
+ * <p>For more information on how to obtain and use instances of Mac, see {@link
+ * com.google.crypto.tink.KeysetHandle#getPrimitive}.
  *
  * @since 1.0.0
  */
 public final class MacConfig {
   public static final String HMAC_TYPE_URL = new HmacKeyManager().getKeyType();
 
-  /** @deprecated */
-  @Deprecated
-  public static final RegistryConfig TINK_1_0_0 = RegistryConfig.getDefaultInstance();
+  /** @deprecated use {@link #register} */
+  @Deprecated public static final RegistryConfig TINK_1_0_0 = RegistryConfig.getDefaultInstance();
 
   /**
-   * @deprecated
+   * @deprecated use {@link #register}
    * @since 1.1.0
    */
-  @Deprecated
-  public static final RegistryConfig TINK_1_1_0 = TINK_1_0_0;
+  @Deprecated public static final RegistryConfig TINK_1_1_0 = TINK_1_0_0;
 
   /**
-   * @deprecated
+   * @deprecated use {@link #register}
    * @since 1.2.0
    */
-  @Deprecated
-  public static final RegistryConfig LATEST = TINK_1_0_0;
+  @Deprecated public static final RegistryConfig LATEST = TINK_1_0_0;
 
   static {
     try {
@@ -64,7 +62,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} and {@link com.google.crypto.tink.KeyManager} needed to
    * handle Mac key types supported in Tink.
    *
@@ -76,7 +74,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} and {@link com.google.crypto.tink.KeyManager} needed to
    * handle Mac key types supported in Tink.
    *
@@ -89,7 +87,8 @@
   }
 
   /**
-   * Registers with the {@code Registry} all Mac key types released with the latest version of Tink.
+   * Registers with the {@code com.google.crypto.tink.Registry} all Mac key types released with the
+   * latest version of Tink.
    *
    * <p>Deprecated-yet-still-supported key types are registered in so-called "no new key"-mode,
    * which allows for usage of existing keys forbids generation of new key material.
@@ -100,4 +99,6 @@
   public static void registerStandardKeyTypes() throws GeneralSecurityException {
     register();
   }
+
+  private MacConfig() {}
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/mac/MacKeyTemplates.java b/java_src/src/main/java/com/google/crypto/tink/mac/MacKeyTemplates.java
index 9c86781..8247160 100644
--- a/java_src/src/main/java/com/google/crypto/tink/mac/MacKeyTemplates.java
+++ b/java_src/src/main/java/com/google/crypto/tink/mac/MacKeyTemplates.java
@@ -139,4 +139,6 @@
         .setOutputPrefixType(OutputPrefixType.TINK)
         .build();
   }
+
+  private MacKeyTemplates() {}
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/mac/MacWrapper.java b/java_src/src/main/java/com/google/crypto/tink/mac/MacWrapper.java
index 1d0f68b..0e01395 100644
--- a/java_src/src/main/java/com/google/crypto/tink/mac/MacWrapper.java
+++ b/java_src/src/main/java/com/google/crypto/tink/mac/MacWrapper.java
@@ -67,7 +67,7 @@
         // clearly insecure, thus should be discouraged.
         throw new GeneralSecurityException("tag too short");
       }
-      byte[] prefix = Arrays.copyOfRange(mac, 0, CryptoFormat.NON_RAW_PREFIX_SIZE);
+      byte[] prefix = Arrays.copyOf(mac, CryptoFormat.NON_RAW_PREFIX_SIZE);
       byte[] macNoPrefix = Arrays.copyOfRange(mac, CryptoFormat.NON_RAW_PREFIX_SIZE, mac.length);
       List<PrimitiveSet.Entry<Mac>> entries = primitives.getPrimitive(prefix);
       for (PrimitiveSet.Entry<Mac> entry : entries) {
@@ -80,7 +80,7 @@
           // If there is no exception, the MAC is valid and we can return.
           return;
         } catch (GeneralSecurityException e) {
-          logger.info("tag prefix matches a key, but cannot verify: " + e.toString());
+          logger.info("tag prefix matches a key, but cannot verify: " + e);
           // Ignored as we want to continue verification with the remaining keys.
         }
       }
diff --git a/java_src/src/main/java/com/google/crypto/tink/prf/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/prf/BUILD.bazel
index 9b63da2..a626133 100644
--- a/java_src/src/main/java/com/google/crypto/tink/prf/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/prf/BUILD.bazel
@@ -2,7 +2,7 @@
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
 
 java_library(
     name = "hkdf_prf_key_manager",
@@ -13,8 +13,12 @@
         "//proto:common_java_proto",
         "//proto:hkdf_prf_java_proto",
         "//proto:tink_java_proto",
-        "//src/main/java/com/google/crypto/tink:core",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:enums",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
         "//src/main/java/com/google/crypto/tink/subtle/prf:hkdf_streaming_prf",
         "//src/main/java/com/google/crypto/tink/subtle/prf:prf_impl",
         "//src/main/java/com/google/crypto/tink/subtle/prf:streaming_prf",
@@ -68,7 +72,9 @@
     deps = [
         ":prf_set",
         "//proto:tink_java_proto",
-        "//src/main/java/com/google/crypto/tink:core",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
         "@maven//:com_google_errorprone_error_prone_annotations",
     ],
 )
@@ -82,8 +88,12 @@
         "//proto:common_java_proto_lite",
         "//proto:hkdf_prf_java_proto_lite",
         "//proto:tink_java_proto_lite",
-        "//src/main/java/com/google/crypto/tink:core-android",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:enums",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
         "//src/main/java/com/google/crypto/tink/subtle/prf:hkdf_streaming_prf",
         "//src/main/java/com/google/crypto/tink/subtle/prf:prf_impl",
         "//src/main/java/com/google/crypto/tink/subtle/prf:streaming_prf",
@@ -120,7 +130,9 @@
     deps = [
         ":prf_set",
         "//proto:tink_java_proto_lite",
-        "//src/main/java/com/google/crypto/tink:core-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
         "@maven//:com_google_errorprone_error_prone_annotations",
     ],
 )
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/signature/BUILD.bazel
index 0449ec4..05c684f 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/BUILD.bazel
@@ -1,8 +1,575 @@
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "signature_config",
+    srcs = ["SignatureConfig.java"],
+    deps = [
+        ":ecdsa_sign_key_manager",
+        ":ecdsa_verify_key_manager",
+        ":ed25519_private_key_manager",
+        ":ed25519_public_key_manager",
+        ":public_key_sign_wrapper",
+        ":public_key_verify_wrapper",
+        ":rsa_ssa_pkcs1_sign_key_manager",
+        ":rsa_ssa_pkcs1_verify_key_manager",
+        ":rsa_ssa_pss_sign_key_manager",
+        "//proto:config_java_proto",
+    ],
+)
+
+java_library(
+    name = "sig_util",
+    srcs = ["SigUtil.java"],
+    deps = [
+        "//proto:common_java_proto",
+        "//proto:ecdsa_java_proto",
+        "//proto:rsa_ssa_pkcs1_java_proto",
+        "//proto:rsa_ssa_pss_java_proto",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:enums",
+    ],
+)
+
+java_library(
+    name = "ecdsa_verify_key_manager",
+    srcs = ["EcdsaVerifyKeyManager.java"],
+    deps = [
+        ":sig_util",
+        "//proto:ecdsa_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:ecdsa_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "signature_pem_keyset_reader",
+    srcs = ["SignaturePemKeysetReader.java"],
+    deps = [
+        ":ecdsa_verify_key_manager",
+        ":rsa_ssa_pkcs1_verify_key_manager",
+        ":rsa_ssa_pss_verify_key_manager",
+        "//proto:common_java_proto",
+        "//proto:ecdsa_java_proto",
+        "//proto:rsa_ssa_pkcs1_java_proto",
+        "//proto:rsa_ssa_pss_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:keyset_reader",
+        "//src/main/java/com/google/crypto/tink/subtle:pem_key_type",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "rsa_ssa_pss_verify_key_manager",
+    srcs = ["RsaSsaPssVerifyKeyManager.java"],
+    deps = [
+        ":sig_util",
+        "//proto:rsa_ssa_pss_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pss_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "ecdsa_sign_key_manager",
+    srcs = ["EcdsaSignKeyManager.java"],
+    deps = [
+        ":ecdsa_verify_key_manager",
+        ":sig_util",
+        "//proto:common_java_proto",
+        "//proto:ecdsa_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:ecdsa_sign_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "public_key_sign_wrapper",
+    srcs = ["PublicKeySignWrapper.java"],
+    deps = [
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:crypto_format",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+java_library(
+    name = "public_key_verify_config",
+    srcs = ["PublicKeyVerifyConfig.java"],
+    deps = [
+        ":signature_config",
+        "//src/main/java/com/google/crypto/tink:config",
+    ],
+)
+
+java_library(
+    name = "signature_key_templates",
+    srcs = ["SignatureKeyTemplates.java"],
+    deps = [
+        ":ecdsa_sign_key_manager",
+        ":ed25519_private_key_manager",
+        ":rsa_ssa_pkcs1_sign_key_manager",
+        ":rsa_ssa_pss_sign_key_manager",
+        "//proto:common_java_proto",
+        "//proto:ecdsa_java_proto",
+        "//proto:rsa_ssa_pkcs1_java_proto",
+        "//proto:rsa_ssa_pss_java_proto",
+        "//proto:tink_java_proto",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "rsa_ssa_pkcs1_sign_key_manager",
+    srcs = ["RsaSsaPkcs1SignKeyManager.java"],
+    deps = [
+        ":rsa_ssa_pkcs1_verify_key_manager",
+        ":sig_util",
+        "//proto:common_java_proto",
+        "//proto:rsa_ssa_pkcs1_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pkcs1_sign_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pkcs1_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "public_key_sign_factory",
+    srcs = ["PublicKeySignFactory.java"],
+    deps = [
+        ":public_key_sign_wrapper",
+        "//src/main/java/com/google/crypto/tink:key_manager",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+    ],
+)
+
+java_library(
+    name = "public_key_sign_config",
+    srcs = ["PublicKeySignConfig.java"],
+    deps = [
+        ":signature_config",
+        "//src/main/java/com/google/crypto/tink:config",
+    ],
+)
+
+java_library(
+    name = "public_key_verify_wrapper",
+    srcs = ["PublicKeyVerifyWrapper.java"],
+    deps = [
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:crypto_format",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+java_library(
+    name = "rsa_ssa_pss_sign_key_manager",
+    srcs = ["RsaSsaPssSignKeyManager.java"],
+    deps = [
+        ":rsa_ssa_pss_verify_key_manager",
+        ":sig_util",
+        "//proto:common_java_proto",
+        "//proto:rsa_ssa_pss_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pss_sign_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pss_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "ed25519_private_key_manager",
+    srcs = ["Ed25519PrivateKeyManager.java"],
+    deps = [
+        ":ed25519_public_key_manager",
+        "//proto:ed25519_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:ed25519_sign",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "rsa_ssa_pkcs1_verify_key_manager",
+    srcs = ["RsaSsaPkcs1VerifyKeyManager.java"],
+    deps = [
+        ":sig_util",
+        "//proto:rsa_ssa_pkcs1_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pkcs1_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "public_key_verify_factory",
+    srcs = ["PublicKeyVerifyFactory.java"],
+    deps = [
+        ":public_key_verify_wrapper",
+        "//src/main/java/com/google/crypto/tink:key_manager",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+    ],
+)
+
+java_library(
+    name = "ed25519_public_key_manager",
+    srcs = ["Ed25519PublicKeyManager.java"],
+    deps = [
+        "//proto:ed25519_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:ed25519_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+# Android packages
+
+android_library(
+    name = "signature_config-android",
+    srcs = ["SignatureConfig.java"],
+    deps = [
+        ":ecdsa_sign_key_manager-android",
+        ":ecdsa_verify_key_manager-android",
+        ":ed25519_private_key_manager-android",
+        ":ed25519_public_key_manager-android",
+        ":public_key_sign_wrapper-android",
+        ":public_key_verify_wrapper-android",
+        ":rsa_ssa_pkcs1_sign_key_manager-android",
+        ":rsa_ssa_pkcs1_verify_key_manager-android",
+        ":rsa_ssa_pss_sign_key_manager-android",
+        "//proto:config_java_proto_lite",
+    ],
+)
+
+android_library(
+    name = "sig_util-android",
+    srcs = ["SigUtil.java"],
+    deps = [
+        "//proto:common_java_proto_lite",
+        "//proto:ecdsa_java_proto_lite",
+        "//proto:rsa_ssa_pkcs1_java_proto_lite",
+        "//proto:rsa_ssa_pss_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:enums",
+    ],
+)
+
+android_library(
+    name = "ecdsa_verify_key_manager-android",
+    srcs = ["EcdsaVerifyKeyManager.java"],
+    deps = [
+        ":sig_util-android",
+        "//proto:ecdsa_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:ecdsa_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "signature_pem_keyset_reader-android",
+    srcs = ["SignaturePemKeysetReader.java"],
+    deps = [
+        ":ecdsa_verify_key_manager-android",
+        ":rsa_ssa_pkcs1_verify_key_manager-android",
+        ":rsa_ssa_pss_verify_key_manager-android",
+        "//proto:common_java_proto_lite",
+        "//proto:ecdsa_java_proto_lite",
+        "//proto:rsa_ssa_pkcs1_java_proto_lite",
+        "//proto:rsa_ssa_pss_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:keyset_reader-android",
+        "//src/main/java/com/google/crypto/tink/subtle:pem_key_type",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "rsa_ssa_pss_verify_key_manager-android",
+    srcs = ["RsaSsaPssVerifyKeyManager.java"],
+    deps = [
+        ":sig_util-android",
+        "//proto:rsa_ssa_pss_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pss_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "ecdsa_sign_key_manager-android",
+    srcs = ["EcdsaSignKeyManager.java"],
+    deps = [
+        ":ecdsa_verify_key_manager-android",
+        ":sig_util-android",
+        "//proto:common_java_proto_lite",
+        "//proto:ecdsa_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:ecdsa_sign_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "public_key_sign_wrapper-android",
+    srcs = ["PublicKeySignWrapper.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:crypto_format-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+android_library(
+    name = "public_key_verify_config-android",
+    srcs = ["PublicKeyVerifyConfig.java"],
+    deps = [
+        ":signature_config-android",
+        "//src/main/java/com/google/crypto/tink:config-android",
+    ],
+)
+
+android_library(
+    name = "signature_key_templates-android",
+    srcs = ["SignatureKeyTemplates.java"],
+    deps = [
+        ":ecdsa_sign_key_manager-android",
+        ":ed25519_private_key_manager-android",
+        ":rsa_ssa_pkcs1_sign_key_manager-android",
+        ":rsa_ssa_pss_sign_key_manager-android",
+        "//proto:common_java_proto_lite",
+        "//proto:ecdsa_java_proto_lite",
+        "//proto:rsa_ssa_pkcs1_java_proto_lite",
+        "//proto:rsa_ssa_pss_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "rsa_ssa_pkcs1_sign_key_manager-android",
+    srcs = ["RsaSsaPkcs1SignKeyManager.java"],
+    deps = [
+        ":rsa_ssa_pkcs1_verify_key_manager-android",
+        ":sig_util-android",
+        "//proto:common_java_proto_lite",
+        "//proto:rsa_ssa_pkcs1_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pkcs1_sign_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pkcs1_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "public_key_sign_factory-android",
+    srcs = ["PublicKeySignFactory.java"],
+    deps = [
+        ":public_key_sign_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:key_manager-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+    ],
+)
+
+android_library(
+    name = "public_key_sign_config-android",
+    srcs = ["PublicKeySignConfig.java"],
+    deps = [
+        ":signature_config-android",
+        "//src/main/java/com/google/crypto/tink:config-android",
+    ],
+)
+
+android_library(
+    name = "public_key_verify_wrapper-android",
+    srcs = ["PublicKeyVerifyWrapper.java"],
+    deps = [
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:crypto_format-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+    ],
+)
+
+android_library(
+    name = "rsa_ssa_pss_sign_key_manager-android",
+    srcs = ["RsaSsaPssSignKeyManager.java"],
+    deps = [
+        ":rsa_ssa_pss_verify_key_manager-android",
+        ":sig_util-android",
+        "//proto:common_java_proto_lite",
+        "//proto:rsa_ssa_pss_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pss_sign_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pss_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "ed25519_private_key_manager-android",
+    srcs = ["Ed25519PrivateKeyManager.java"],
+    deps = [
+        ":ed25519_public_key_manager-android",
+        "//proto:ed25519_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/subtle:ed25519_sign",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "rsa_ssa_pkcs1_verify_key_manager-android",
+    srcs = ["RsaSsaPkcs1VerifyKeyManager.java"],
+    deps = [
+        ":sig_util-android",
+        "//proto:rsa_ssa_pkcs1_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pkcs1_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "public_key_verify_factory-android",
+    srcs = ["PublicKeyVerifyFactory.java"],
+    deps = [
+        ":public_key_verify_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:key_manager-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+    ],
+)
+
+android_library(
+    name = "ed25519_public_key_manager-android",
+    srcs = ["Ed25519PublicKeyManager.java"],
+    deps = [
+        "//proto:ed25519_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:ed25519_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+# Deprecated, will be removed soon.
 
 filegroup(
     name = "srcs",
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/EcdsaSignKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/signature/EcdsaSignKeyManager.java
index c03f3ef..41574b7 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/EcdsaSignKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/EcdsaSignKeyManager.java
@@ -17,6 +17,7 @@
 package com.google.crypto.tink.signature;
 
 import com.google.crypto.tink.KeyTemplate;
+import com.google.crypto.tink.KeyTypeManager;
 import com.google.crypto.tink.PrivateKeyTypeManager;
 import com.google.crypto.tink.PublicKeySign;
 import com.google.crypto.tink.Registry;
@@ -50,7 +51,7 @@
     super(
         EcdsaPrivateKey.class,
         EcdsaPublicKey.class,
-        new PrimitiveFactory<PublicKeySign, EcdsaPrivateKey>(PublicKeySign.class) {
+        new KeyTypeManager.PrimitiveFactory<PublicKeySign, EcdsaPrivateKey>(PublicKeySign.class) {
           @Override
           public PublicKeySign getPrimitive(EcdsaPrivateKey key) throws GeneralSecurityException {
             ECPrivateKey privateKey =
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManager.java
index d8205b3..c266e34 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/EcdsaVerifyKeyManager.java
@@ -37,7 +37,8 @@
   public EcdsaVerifyKeyManager() {
     super(
         EcdsaPublicKey.class,
-        new PrimitiveFactory<PublicKeyVerify, EcdsaPublicKey>(PublicKeyVerify.class) {
+        new KeyTypeManager.PrimitiveFactory<PublicKeyVerify, EcdsaPublicKey>(
+            PublicKeyVerify.class) {
           @Override
           public PublicKeyVerify getPrimitive(EcdsaPublicKey keyProto)
               throws GeneralSecurityException {
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManager.java
index 34b83f1..cd90ee6 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/Ed25519PrivateKeyManager.java
@@ -17,6 +17,7 @@
 package com.google.crypto.tink.signature;
 
 import com.google.crypto.tink.KeyTemplate;
+import com.google.crypto.tink.KeyTypeManager;
 import com.google.crypto.tink.PrivateKeyTypeManager;
 import com.google.crypto.tink.PublicKeySign;
 import com.google.crypto.tink.Registry;
@@ -41,7 +42,7 @@
     super(
         Ed25519PrivateKey.class,
         Ed25519PublicKey.class,
-        new PrimitiveFactory<PublicKeySign, Ed25519PrivateKey>(PublicKeySign.class) {
+        new KeyTypeManager.PrimitiveFactory<PublicKeySign, Ed25519PrivateKey>(PublicKeySign.class) {
           @Override
           public PublicKeySign getPrimitive(Ed25519PrivateKey keyProto)
               throws GeneralSecurityException {
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/Ed25519PublicKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/signature/Ed25519PublicKeyManager.java
index e0a0775..de74b1d 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/Ed25519PublicKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/Ed25519PublicKeyManager.java
@@ -35,7 +35,8 @@
   public Ed25519PublicKeyManager() {
     super(
         Ed25519PublicKey.class,
-        new PrimitiveFactory<PublicKeyVerify, Ed25519PublicKey>(PublicKeyVerify.class) {
+        new KeyTypeManager.PrimitiveFactory<PublicKeyVerify, Ed25519PublicKey>(
+            PublicKeyVerify.class) {
           @Override
           public PublicKeyVerify getPrimitive(Ed25519PublicKey keyProto) {
             return new Ed25519Verify(keyProto.getKeyValue().toByteArray());
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManager.java
index fa97b2b..3afa77a 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1SignKeyManager.java
@@ -17,6 +17,7 @@
 package com.google.crypto.tink.signature;
 
 import com.google.crypto.tink.KeyTemplate;
+import com.google.crypto.tink.KeyTypeManager;
 import com.google.crypto.tink.PrivateKeyTypeManager;
 import com.google.crypto.tink.PublicKeySign;
 import com.google.crypto.tink.Registry;
@@ -36,7 +37,6 @@
 import java.math.BigInteger;
 import java.nio.charset.Charset;
 import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.interfaces.RSAPrivateCrtKey;
@@ -58,7 +58,8 @@
     super(
         RsaSsaPkcs1PrivateKey.class,
         RsaSsaPkcs1PublicKey.class,
-        new PrimitiveFactory<PublicKeySign, RsaSsaPkcs1PrivateKey>(PublicKeySign.class) {
+        new KeyTypeManager.PrimitiveFactory<PublicKeySign, RsaSsaPkcs1PrivateKey>(
+            PublicKeySign.class) {
           @Override
           public PublicKeySign getPrimitive(RsaSsaPkcs1PrivateKey keyProto)
               throws GeneralSecurityException {
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManager.java
index 08c1a05..efe1e77 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPkcs1VerifyKeyManager.java
@@ -39,7 +39,8 @@
   public RsaSsaPkcs1VerifyKeyManager() {
     super(
         RsaSsaPkcs1PublicKey.class,
-        new PrimitiveFactory<PublicKeyVerify, RsaSsaPkcs1PublicKey>(PublicKeyVerify.class) {
+        new KeyTypeManager.PrimitiveFactory<PublicKeyVerify, RsaSsaPkcs1PublicKey>(
+            PublicKeyVerify.class) {
           @Override
           public PublicKeyVerify getPrimitive(RsaSsaPkcs1PublicKey keyProto)
               throws GeneralSecurityException {
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManager.java
index 6135789..9dc6532 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManager.java
@@ -17,6 +17,7 @@
 package com.google.crypto.tink.signature;
 
 import com.google.crypto.tink.KeyTemplate;
+import com.google.crypto.tink.KeyTypeManager;
 import com.google.crypto.tink.PrivateKeyTypeManager;
 import com.google.crypto.tink.PublicKeySign;
 import com.google.crypto.tink.Registry;
@@ -36,7 +37,6 @@
 import java.math.BigInteger;
 import java.nio.charset.Charset;
 import java.security.GeneralSecurityException;
-import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.interfaces.RSAPrivateCrtKey;
@@ -58,7 +58,8 @@
     super(
         RsaSsaPssPrivateKey.class,
         RsaSsaPssPublicKey.class,
-        new PrimitiveFactory<PublicKeySign, RsaSsaPssPrivateKey>(PublicKeySign.class) {
+        new KeyTypeManager.PrimitiveFactory<PublicKeySign, RsaSsaPssPrivateKey>(
+            PublicKeySign.class) {
           @Override
           public PublicKeySign getPrimitive(RsaSsaPssPrivateKey keyProto)
               throws GeneralSecurityException {
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManager.java b/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManager.java
index d370178..907f262 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManager.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/RsaSsaPssVerifyKeyManager.java
@@ -40,7 +40,8 @@
   public RsaSsaPssVerifyKeyManager() {
     super(
         RsaSsaPssPublicKey.class,
-        new PrimitiveFactory<PublicKeyVerify, RsaSsaPssPublicKey>(PublicKeyVerify.class) {
+        new KeyTypeManager.PrimitiveFactory<PublicKeyVerify, RsaSsaPssPublicKey>(
+            PublicKeyVerify.class) {
           @Override
           public PublicKeyVerify getPrimitive(RsaSsaPssPublicKey keyProto)
               throws GeneralSecurityException {
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/SigUtil.java b/java_src/src/main/java/com/google/crypto/tink/signature/SigUtil.java
index 3500417..d42f155 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/SigUtil.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/SigUtil.java
@@ -108,6 +108,9 @@
     if (params.getSigHash() != params.getMgf1Hash()) {
       throw new GeneralSecurityException("MGF1 hash is different from signature hash");
     }
+    if (params.getSaltLength() < 0) {
+      throw new GeneralSecurityException("salt length is negative");
+    }
   }
 
   /** Converts protobuf enum {@code HashType} to raw Java enum {@code Enums.HashType}. */
@@ -136,7 +139,7 @@
       case NIST_P521:
         return EllipticCurves.CurveType.NIST_P521;
       default:
-        throw new GeneralSecurityException("unknown curve type: " + type);
+        throw new GeneralSecurityException("unknown curve type: " + type.name());
     }
   }
 
@@ -152,7 +155,7 @@
       case DER:
         return EllipticCurves.EcdsaEncoding.DER;
       default:
-        throw new GeneralSecurityException("unknown ECDSA encoding: " + encoding);
+        throw new GeneralSecurityException("unknown ECDSA encoding: " + encoding.name());
     }
   }
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/signature/SignatureConfig.java b/java_src/src/main/java/com/google/crypto/tink/signature/SignatureConfig.java
index 9d6b05c..0f2804a 100644
--- a/java_src/src/main/java/com/google/crypto/tink/signature/SignatureConfig.java
+++ b/java_src/src/main/java/com/google/crypto/tink/signature/SignatureConfig.java
@@ -16,14 +16,13 @@
 
 package com.google.crypto.tink.signature;
 
-import com.google.crypto.tink.Registry;
 import com.google.crypto.tink.proto.RegistryConfig;
 import java.security.GeneralSecurityException;
 
 /**
- * Static methods and constants for registering with the {@link Registry} all instances of {@link
- * com.google.crypto.tink.PublicKeySign} and {@link com.google.crypto.tink.PublicKeyVerify} key
- * types supported in a particular release of Tink.
+ * Static methods and constants for registering with the {@link com.google.crypto.tink.Registry} all
+ * instances of {@link com.google.crypto.tink.PublicKeySign} and {@link
+ * com.google.crypto.tink.PublicKeyVerify} key types supported in a particular release of Tink.
  *
  * <p>To register all PublicKeySign and PublicKeyVerify key types provided in the latest Tink
  * version one can do:
@@ -71,7 +70,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} needed to handle PublicKeySign and PublicKeyVerify key types
    * supported in Tink.
    *
@@ -83,7 +82,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} needed to handle PublicKeySign and PublicKeyVerify key types
    * supported in Tink.
    *
diff --git a/java_src/src/main/java/com/google/crypto/tink/streamingaead/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/streamingaead/BUILD.bazel
index ae5e82a..4be763e 100644
--- a/java_src/src/main/java/com/google/crypto/tink/streamingaead/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/streamingaead/BUILD.bazel
@@ -1,8 +1,287 @@
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "streaming_aead_util",
+    srcs = ["StreamingAeadUtil.java"],
+    deps = ["//proto:common_java_proto"],
+)
+
+java_library(
+    name = "streaming_aead_key_templates",
+    srcs = ["StreamingAeadKeyTemplates.java"],
+    deps = [
+        ":aes_ctr_hmac_streaming_key_manager",
+        ":aes_gcm_hkdf_streaming_key_manager",
+        "//proto:aes_ctr_hmac_streaming_java_proto",
+        "//proto:aes_gcm_hkdf_streaming_java_proto",
+        "//proto:common_java_proto",
+        "//proto:hmac_java_proto",
+        "//proto:tink_java_proto",
+    ],
+)
+
+java_library(
+    name = "streaming_aead_config",
+    srcs = ["StreamingAeadConfig.java"],
+    deps = [
+        ":aes_ctr_hmac_streaming_key_manager",
+        ":aes_gcm_hkdf_streaming_key_manager",
+        ":streaming_aead_wrapper",
+        "//proto:config_java_proto",
+    ],
+)
+
+java_library(
+    name = "seekable_byte_channel_decrypter",
+    srcs = ["SeekableByteChannelDecrypter.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "@maven//:com_google_code_findbugs_jsr305",
+    ],
+)
+
+java_library(
+    name = "input_stream_decrypter",
+    srcs = ["InputStreamDecrypter.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "@maven//:com_google_code_findbugs_jsr305",
+    ],
+)
+
+java_library(
+    name = "streaming_aead_helper",
+    srcs = ["StreamingAeadHelper.java"],
+    deps = [
+        ":input_stream_decrypter",
+        ":readable_byte_channel_decrypter",
+        ":seekable_byte_channel_decrypter",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+    ],
+)
+
+java_library(
+    name = "streaming_aead_factory",
+    srcs = ["StreamingAeadFactory.java"],
+    deps = [
+        ":streaming_aead_wrapper",
+        "//src/main/java/com/google/crypto/tink:key_manager",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+    ],
+)
+
+java_library(
+    name = "aes_gcm_hkdf_streaming_key_manager",
+    srcs = ["AesGcmHkdfStreamingKeyManager.java"],
+    deps = [
+        ":streaming_aead_util",
+        "//proto:aes_gcm_hkdf_streaming_java_proto",
+        "//proto:common_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_gcm_hkdf_streaming",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "aes_ctr_hmac_streaming_key_manager",
+    srcs = ["AesCtrHmacStreamingKeyManager.java"],
+    deps = [
+        ":streaming_aead_util",
+        "//proto:aes_ctr_hmac_streaming_java_proto",
+        "//proto:common_java_proto",
+        "//proto:hmac_java_proto",
+        "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_ctr_hmac_streaming",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+java_library(
+    name = "readable_byte_channel_decrypter",
+    srcs = ["ReadableByteChannelDecrypter.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "//src/main/java/com/google/crypto/tink/subtle:rewindable_readable_byte_channel",
+        "@maven//:com_google_code_findbugs_jsr305",
+    ],
+)
+
+java_library(
+    name = "streaming_aead_wrapper",
+    srcs = ["StreamingAeadWrapper.java"],
+    deps = [
+        ":streaming_aead_helper",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+    ],
+)
+
+# Android libraries
+
+android_library(
+    name = "streaming_aead_util-android",
+    srcs = ["StreamingAeadUtil.java"],
+    deps = ["//proto:common_java_proto_lite"],
+)
+
+android_library(
+    name = "streaming_aead_key_templates-android",
+    srcs = ["StreamingAeadKeyTemplates.java"],
+    deps = [
+        ":aes_ctr_hmac_streaming_key_manager-android",
+        ":aes_gcm_hkdf_streaming_key_manager-android",
+        "//proto:aes_ctr_hmac_streaming_java_proto_lite",
+        "//proto:aes_gcm_hkdf_streaming_java_proto_lite",
+        "//proto:common_java_proto_lite",
+        "//proto:hmac_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+    ],
+)
+
+android_library(
+    name = "streaming_aead_config-android",
+    srcs = ["StreamingAeadConfig.java"],
+    deps = [
+        ":aes_ctr_hmac_streaming_key_manager-android",
+        ":aes_gcm_hkdf_streaming_key_manager-android",
+        ":streaming_aead_wrapper-android",
+        "//proto:config_java_proto_lite",
+    ],
+)
+
+android_library(
+    name = "seekable_byte_channel_decrypter-android",
+    srcs = ["SeekableByteChannelDecrypter.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "@maven//:com_google_code_findbugs_jsr305",
+    ],
+)
+
+android_library(
+    name = "input_stream_decrypter-android",
+    srcs = ["InputStreamDecrypter.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "@maven//:com_google_code_findbugs_jsr305",
+    ],
+)
+
+android_library(
+    name = "streaming_aead_helper-android",
+    srcs = ["StreamingAeadHelper.java"],
+    deps = [
+        ":input_stream_decrypter-android",
+        ":readable_byte_channel_decrypter-android",
+        ":seekable_byte_channel_decrypter-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+    ],
+)
+
+android_library(
+    name = "streaming_aead_factory-android",
+    srcs = ["StreamingAeadFactory.java"],
+    deps = [
+        ":streaming_aead_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:key_manager-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+    ],
+)
+
+android_library(
+    name = "aes_gcm_hkdf_streaming_key_manager-android",
+    srcs = ["AesGcmHkdfStreamingKeyManager.java"],
+    deps = [
+        ":streaming_aead_util-android",
+        "//proto:aes_gcm_hkdf_streaming_java_proto_lite",
+        "//proto:common_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_gcm_hkdf_streaming",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "aes_ctr_hmac_streaming_key_manager-android",
+    srcs = ["AesCtrHmacStreamingKeyManager.java"],
+    deps = [
+        ":streaming_aead_util-android",
+        "//proto:aes_ctr_hmac_streaming_java_proto_lite",
+        "//proto:common_java_proto_lite",
+        "//proto:hmac_java_proto_lite",
+        "//proto:tink_java_proto_lite",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_ctr_hmac_streaming",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "@com_google_protobuf//:protobuf_javalite",
+    ],
+)
+
+android_library(
+    name = "readable_byte_channel_decrypter-android",
+    srcs = ["ReadableByteChannelDecrypter.java"],
+    deps = [
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "//src/main/java/com/google/crypto/tink/subtle:rewindable_readable_byte_channel",
+        "@maven//:com_google_code_findbugs_jsr305",
+    ],
+)
+
+android_library(
+    name = "streaming_aead_wrapper-android",
+    srcs = ["StreamingAeadWrapper.java"],
+    deps = [
+        ":streaming_aead_helper-android",
+        "//src/main/java/com/google/crypto/tink:primitive_set-android",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+    ],
+)
+
+# Deprecated, will be removed soon.
 
 filegroup(
     name = "srcs",
diff --git a/java_src/src/main/java/com/google/crypto/tink/streamingaead/ReadableByteChannelDecrypter.java b/java_src/src/main/java/com/google/crypto/tink/streamingaead/ReadableByteChannelDecrypter.java
index ed3c2fe..273d361 100644
--- a/java_src/src/main/java/com/google/crypto/tink/streamingaead/ReadableByteChannelDecrypter.java
+++ b/java_src/src/main/java/com/google/crypto/tink/streamingaead/ReadableByteChannelDecrypter.java
@@ -64,9 +64,7 @@
     this.associatedData = associatedData.clone();
   }
 
-  @SuppressWarnings("GuardedBy")
   @Override
-  @GuardedBy("this")
   public synchronized int read(ByteBuffer dst) throws IOException {
     if (dst.remaining() == 0) {
       return 0;
@@ -87,8 +85,6 @@
           if (retValue > 0) {
             // Found a matching channel
             matchingChannel = attemptedChannel;
-            // TODO(b/145386688): This access should be guarded by 'this.ciphertextChannel'; instead
-            // found: 'this'
             ciphertextChannel.disableRewinding();
           } else if (retValue == 0) {
             // Not clear whether the channel could be matched: it might be
@@ -96,8 +92,6 @@
             // to check the header, or maybe the header was checked, but there
             // were no actual encrypted bytes in the channel yet.
             // Should try again.
-            // TODO(b/145386688): This access should be guarded by 'this.ciphertextChannel'; instead
-            // found: 'this'
             ciphertextChannel.rewind();
             attemptedMatching = false;
           }
@@ -107,14 +101,10 @@
           // IOException is thrown e.g. when MAC is incorrect, but also in case
           // of I/O failures.
           // TODO(b/66098906): Use a subclass of IOException.
-          // TODO(b/145386688): This access should be guarded by 'this.ciphertextChannel'; instead
-          // found: 'this'
           ciphertextChannel.rewind();
           continue;
         } catch (GeneralSecurityException e) {
           // Try another key.
-          // TODO(b/145386688): This access should be guarded by 'this.ciphertextChannel'; instead
-          // found: 'this'
           ciphertextChannel.rewind();
           continue;
         }
@@ -123,21 +113,13 @@
     }
   }
 
-  @SuppressWarnings("GuardedBy")
   @Override
-  @GuardedBy("this")
   public synchronized void close() throws IOException {
-    // TODO(b/145386688): This access should be guarded by 'this.ciphertextChannel'; instead found:
-    // 'this'
     ciphertextChannel.close();
   }
 
-  @SuppressWarnings("GuardedBy")
   @Override
-  @GuardedBy("this")
   public synchronized boolean isOpen() {
-    // TODO(b/145386688): This access should be guarded by 'this.ciphertextChannel'; instead found:
-    // 'this'
     return ciphertextChannel.isOpen();
   }
 }
diff --git a/java_src/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadConfig.java b/java_src/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadConfig.java
index 7994922..5547062 100644
--- a/java_src/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadConfig.java
+++ b/java_src/src/main/java/com/google/crypto/tink/streamingaead/StreamingAeadConfig.java
@@ -16,13 +16,13 @@
 
 package com.google.crypto.tink.streamingaead;
 
-import com.google.crypto.tink.Registry;
 import com.google.crypto.tink.proto.RegistryConfig;
 import java.security.GeneralSecurityException;
 
 /**
- * Static methods and constants for registering with the {@link Registry} all instances of {@link
- * com.google.crypto.tink.StreamingAead} key types supported in a particular release of Tink.
+ * Static methods and constants for registering with the {@link com.google.crypto.tink.Registry} all
+ * instances of {@link com.google.crypto.tink.StreamingAead} key types supported in a particular
+ * release of Tink.
  *
  * <p>To register all StreamingAead key types provided in the latest Tink version one can do:
  *
@@ -57,7 +57,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} needed to handle StreamingAead key types supported in Tink.
    *
    * @deprecated use {@link #register}
@@ -68,7 +68,7 @@
   }
 
   /**
-   * Tries to register with the {@link Registry} all instances of {@link
+   * Tries to register with the {@link com.google.crypto.tink.Registry} all instances of {@link
    * com.google.crypto.tink.Catalogue} needed to handle StreamingAead key types supported in Tink.
    *
    * @since 1.2.0
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/AesGcmHkdfStreaming.java b/java_src/src/main/java/com/google/crypto/tink/subtle/AesGcmHkdfStreaming.java
index 59eda8a..17252b1 100644
--- a/java_src/src/main/java/com/google/crypto/tink/subtle/AesGcmHkdfStreaming.java
+++ b/java_src/src/main/java/com/google/crypto/tink/subtle/AesGcmHkdfStreaming.java
@@ -173,16 +173,17 @@
     return Random.randBytes(keySizeInBytes);
   }
 
-  private GCMParameterSpec paramsForSegment(byte[] prefix, int segmentNr, boolean last) {
+  private static GCMParameterSpec paramsForSegment(byte[] prefix, long segmentNr, boolean last)
+      throws GeneralSecurityException {
     ByteBuffer nonce = ByteBuffer.allocate(NONCE_SIZE_IN_BYTES);
     nonce.order(ByteOrder.BIG_ENDIAN);
     nonce.put(prefix);
-    nonce.putInt(segmentNr);
+    SubtleUtil.putAsUnsigedInt(nonce, segmentNr);
     nonce.put((byte) (last ? 1 : 0));
     return new GCMParameterSpec(8 * TAG_SIZE_IN_BYTES, nonce.array());
   }
 
-  private byte[] randomNonce() {
+  private static byte[] randomNonce() {
     return Random.randBytes(NONCE_PREFIX_IN_BYTES);
   }
 
@@ -201,8 +202,8 @@
     private final SecretKeySpec keySpec;
     private final Cipher cipher;
     private final byte[] noncePrefix;
-    private ByteBuffer header;
-    private int encryptedSegments = 0;
+    private final ByteBuffer header;
+    private long encryptedSegments = 0;
 
     public AesGcmHkdfStreamEncrypter(byte[] aad) throws GeneralSecurityException {
       cipher = cipherInstance();
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel
index c70a509..d71f0c9 100644
--- a/java_src/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/subtle/BUILD.bazel
@@ -2,17 +2,510 @@
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
 
-filegroup(
-    name = "srcs",
-    srcs = glob(
-        [
-            "*.java",
-        ],
-    ),
+java_library(
+    name = "aes_cmac",
+    srcs = ["AesCmac.java"],
+    deps = [
+        ":aes_util",
+        ":bytes",
+        ":subtle_util_cluster",
+        ":validators",
+        "//src/main/java/com/google/crypto/tink:mac",
+    ],
 )
 
+java_library(
+    name = "aes_ctr_hmac_streaming",
+    srcs = ["AesCtrHmacStreaming.java"],
+    deps = [
+        ":bytes",
+        ":hkdf",
+        ":nonce_based_streaming_aead_cluster",
+        ":random",
+        ":stream_segment_decrypter",
+        ":stream_segment_encrypter",
+        ":subtle_util_cluster",
+        ":validators",
+    ],
+)
+
+java_library(
+    name = "cha_cha20_poly1305",
+    srcs = ["ChaCha20Poly1305.java"],
+    deps = [
+        ":cha_cha20",
+        ":cha_cha20_base",
+        ":cha_cha20_poly1305_base",
+    ],
+)
+
+java_library(
+    name = "stream_segment_decrypter",
+    srcs = ["StreamSegmentDecrypter.java"],
+)
+
+java_library(
+    name = "aes_ctr_jce_cipher",
+    srcs = ["AesCtrJceCipher.java"],
+    deps = [
+        ":ind_cpa_cipher",
+        ":random",
+        ":subtle_util_cluster",
+        ":validators",
+    ],
+)
+
+java_library(
+    name = "x25519",
+    srcs = ["X25519.java"],
+    # TODO(thaidn): remove this export once java_src/src/test:testlib has been build cleaned.
+    exports = [
+        ":field25519",
+    ],
+    deps = [
+        ":curve25519",
+        ":field25519",
+        ":random",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+    ],
+)
+
+java_library(
+    name = "ecies_hkdf_sender_kem",
+    srcs = ["EciesHkdfSenderKem.java"],
+    deps = [
+        ":elliptic_curves",
+        ":hkdf",
+        ":immutable_byte_array",
+    ],
+)
+
+java_library(
+    name = "aes_util",
+    srcs = ["AesUtil.java"],
+)
+
+java_library(
+    name = "pem_key_type",
+    srcs = ["PemKeyType.java"],
+    deps = [
+        ":base64",
+        ":elliptic_curves",
+        ":enums",
+        ":subtle_util_cluster",
+    ],
+)
+
+java_library(
+    name = "aes_gcm_hkdf_streaming",
+    srcs = ["AesGcmHkdfStreaming.java"],
+    deps = [
+        ":hkdf",
+        ":nonce_based_streaming_aead_cluster",
+        ":random",
+        ":stream_segment_decrypter",
+        ":stream_segment_encrypter",
+        ":subtle_util_cluster",
+        ":validators",
+    ],
+)
+
+java_library(
+    name = "encrypt_then_authenticate",
+    srcs = ["EncryptThenAuthenticate.java"],
+    deps = [
+        ":aes_ctr_jce_cipher",
+        ":bytes",
+        ":ind_cpa_cipher",
+        ":prf_hmac_jce",
+        ":prf_mac",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:mac",
+    ],
+)
+
+java_library(
+    name = "bytes",
+    srcs = ["Bytes.java"],
+)
+
+java_library(
+    name = "immutable_byte_array",
+    srcs = ["ImmutableByteArray.java"],
+)
+
+java_library(
+    name = "elliptic_curves",
+    srcs = ["EllipticCurves.java"],
+    deps = [":subtle_util_cluster"],
+)
+
+java_library(
+    name = "ecies_aead_hkdf_dem_helper",
+    srcs = ["EciesAeadHkdfDemHelper.java"],
+    deps = ["//src/main/java/com/google/crypto/tink:aead"],
+)
+
+java_library(
+    name = "rsa_ssa_pkcs1_verify_jce",
+    srcs = ["RsaSsaPkcs1VerifyJce.java"],
+    deps = [
+        ":bytes",
+        ":enums",
+        ":hex",
+        ":subtle_util_cluster",
+        ":validators",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+    ],
+)
+
+java_library(
+    name = "ecies_aead_hkdf_hybrid_encrypt",
+    srcs = ["EciesAeadHkdfHybridEncrypt.java"],
+    deps = [
+        ":ecies_aead_hkdf_dem_helper",
+        ":ecies_hkdf_sender_kem",
+        ":elliptic_curves",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+    ],
+)
+
+java_library(
+    name = "ecdsa_sign_jce",
+    srcs = ["EcdsaSignJce.java"],
+    deps = [
+        ":elliptic_curves",
+        ":enums",
+        ":subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+    ],
+)
+
+java_library(
+    name = "validators",
+    srcs = ["Validators.java"],
+    deps = [":enums"],
+)
+
+java_library(
+    name = "stream_segment_encrypter",
+    srcs = ["StreamSegmentEncrypter.java"],
+)
+
+java_library(
+    name = "aes_siv",
+    srcs = ["AesSiv.java"],
+    deps = [
+        ":aes_cmac",
+        ":aes_util",
+        ":bytes",
+        ":subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink:deterministic_aead",
+    ],
+)
+
+java_library(
+    name = "ind_cpa_cipher",
+    srcs = ["IndCpaCipher.java"],
+)
+
+java_library(
+    name = "engine_wrapper",
+    srcs = ["EngineWrapper.java"],
+)
+
+java_library(
+    name = "prf_mac",
+    srcs = ["PrfMac.java"],
+    deps = [
+        ":bytes",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink/prf:prf_set",
+        "@maven//:com_google_errorprone_error_prone_annotations",
+    ],
+)
+
+java_library(
+    name = "rsa_ssa_pss_verify_jce",
+    srcs = ["RsaSsaPssVerifyJce.java"],
+    deps = [
+        ":bytes",
+        ":enums",
+        ":subtle_util_cluster",
+        ":validators",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+    ],
+)
+
+java_library(
+    name = "random",
+    srcs = ["Random.java"],
+)
+
+java_library(
+    name = "cha_cha20_base",
+    srcs = ["ChaCha20Base.java"],
+    deps = [
+        ":bytes",
+        ":ind_cpa_cipher",
+        ":random",
+    ],
+)
+
+java_library(
+    name = "poly1305",
+    srcs = ["Poly1305.java"],
+    deps = [":bytes"],
+)
+
+java_library(
+    name = "aes_gcm_jce",
+    srcs = ["AesGcmJce.java"],
+    deps = [
+        ":random",
+        ":subtle_util_cluster",
+        ":validators",
+        "//src/main/java/com/google/crypto/tink:aead",
+    ],
+)
+
+java_library(
+    name = "hex",
+    srcs = ["Hex.java"],
+)
+
+java_library(
+    name = "ed25519_sign",
+    srcs = ["Ed25519Sign.java"],
+    deps = [
+        ":ed25519_cluster",
+        ":field25519",
+        ":random",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+    ],
+)
+
+java_library(
+    name = "ecies_hkdf_recipient_kem",
+    srcs = ["EciesHkdfRecipientKem.java"],
+    deps = [
+        ":elliptic_curves",
+        ":hkdf",
+    ],
+)
+
+java_library(
+    name = "enums",
+    srcs = ["Enums.java"],
+)
+
+java_library(
+    name = "aes_eax_jce",
+    srcs = ["AesEaxJce.java"],
+    deps = [
+        ":random",
+        ":subtle_util_cluster",
+        ":validators",
+        "//src/main/java/com/google/crypto/tink:aead",
+    ],
+)
+
+java_library(
+    name = "ed25519_verify",
+    srcs = ["Ed25519Verify.java"],
+    deps = [
+        ":ed25519_cluster",
+        ":field25519",
+        ":immutable_byte_array",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+    ],
+)
+
+java_library(
+    name = "cha_cha20_poly1305_base",
+    srcs = ["ChaCha20Poly1305Base.java"],
+    deps = [
+        ":cha_cha20_base",
+        ":poly1305",
+        "//src/main/java/com/google/crypto/tink:aead",
+    ],
+)
+
+java_library(
+    name = "x_cha_cha20",
+    srcs = ["XChaCha20.java"],
+    deps = [":cha_cha20_base"],
+)
+
+java_library(
+    name = "rsa_ssa_pkcs1_sign_jce",
+    srcs = ["RsaSsaPkcs1SignJce.java"],
+    deps = [
+        ":enums",
+        ":subtle_util_cluster",
+        ":validators",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+    ],
+)
+
+java_library(
+    name = "field25519",
+    srcs = ["Field25519.java"],
+    deps = ["//src/main/java/com/google/crypto/tink/annotations:alpha"],
+)
+
+java_library(
+    name = "cha_cha20",
+    srcs = ["ChaCha20.java"],
+    deps = [":cha_cha20_base"],
+)
+
+java_library(
+    name = "ecdsa_verify_jce",
+    srcs = ["EcdsaVerifyJce.java"],
+    deps = [
+        ":elliptic_curves",
+        ":enums",
+        ":subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+    ],
+)
+
+java_library(
+    name = "rewindable_readable_byte_channel",
+    srcs = ["RewindableReadableByteChannel.java"],
+    deps = [
+        "@maven//:com_google_code_findbugs_jsr305",
+    ],
+)
+
+java_library(
+    name = "hkdf",
+    srcs = ["Hkdf.java"],
+    deps = [
+        ":bytes",
+        ":subtle_util_cluster",
+    ],
+)
+
+java_library(
+    name = "rsa_ssa_pss_sign_jce",
+    srcs = ["RsaSsaPssSignJce.java"],
+    deps = [
+        ":enums",
+        ":random",
+        ":subtle_util_cluster",
+        ":validators",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+    ],
+)
+
+java_library(
+    name = "base64",
+    srcs = ["Base64.java"],
+)
+
+java_library(
+    name = "prf_hmac_jce",
+    srcs = ["PrfHmacJce.java"],
+    deps = [
+        ":subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/prf:prf_set",
+        "@maven//:com_google_errorprone_error_prone_annotations",
+    ],
+)
+
+java_library(
+    name = "kwp",
+    srcs = ["Kwp.java"],
+    deps = [
+        ":subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink:key_wrap",
+    ],
+)
+
+java_library(
+    name = "x_cha_cha20_poly1305",
+    srcs = ["XChaCha20Poly1305.java"],
+    deps = [
+        ":cha_cha20_base",
+        ":cha_cha20_poly1305_base",
+        ":x_cha_cha20",
+    ],
+)
+
+java_library(
+    name = "ecies_aead_hkdf_hybrid_decrypt",
+    srcs = ["EciesAeadHkdfHybridDecrypt.java"],
+    deps = [
+        ":ecies_aead_hkdf_dem_helper",
+        ":ecies_hkdf_recipient_kem",
+        ":elliptic_curves",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+    ],
+)
+
+java_library(
+    name = "curve25519",
+    srcs = ["Curve25519.java"],
+    deps = [
+        ":bytes",
+        ":field25519",
+        ":hex",
+        "//src/main/java/com/google/crypto/tink/annotations:alpha",
+    ],
+)
+
+java_library(
+    name = "nonce_based_streaming_aead_cluster",
+    srcs = [
+        "NonceBasedStreamingAead.java",
+        "StreamingAeadDecryptingChannel.java",
+        "StreamingAeadDecryptingStream.java",
+        "StreamingAeadEncryptingChannel.java",
+        "StreamingAeadEncryptingStream.java",
+        "StreamingAeadSeekableDecryptingChannel.java",
+    ],
+    deps = [
+        ":stream_segment_decrypter",
+        ":stream_segment_encrypter",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+    ],
+)
+
+java_library(
+    name = "subtle_util_cluster",
+    srcs = [
+        "EngineFactory.java",
+        "SubtleUtil.java",
+    ],
+    deps = [
+        ":engine_wrapper",
+        ":enums",
+        ":validators",
+    ],
+)
+
+java_library(
+    name = "ed25519_cluster",
+    srcs = [
+        "Ed25519.java",
+        "Ed25519Constants.java",
+    ],
+    deps = [
+        ":bytes",
+        ":curve25519",
+        ":field25519",
+        ":subtle_util_cluster",
+    ],
+)
+
+# Deprecated rules, will be deleted soon.
+
 # common subtle
 
 java_library(
@@ -57,7 +550,6 @@
         ":mac",
         ":subtle",
         "//src/main/java/com/google/crypto/tink:primitives",
-        "//src/main/java/com/google/crypto/tink/annotations",
     ],
 )
 
@@ -83,13 +575,15 @@
     srcs = [
         "AesCmac.java",
         "AesUtil.java",
-        "MacJce.java",
+        "PrfHmacJce.java",
+        "PrfMac.java",
     ],
     javacopts = JAVACOPTS_OSS,
     deps = [
         ":subtle",
         "//src/main/java/com/google/crypto/tink:primitives",
         "//src/main/java/com/google/crypto/tink/annotations",
+        "//src/main/java/com/google/crypto/tink/prf:prf_set",
         "@maven//:com_google_errorprone_error_prone_annotations",
     ],
 )
@@ -112,6 +606,7 @@
     ],
     javacopts = JAVACOPTS_OSS,
     deps = [
+        ":curve25519",
         ":subtle",
         ":x25519",
         "//src/main/java/com/google/crypto/tink:primitives",
@@ -176,19 +671,3 @@
         "@maven//:com_google_code_findbugs_jsr305",
     ],
 )
-
-# x25519 subtle
-
-java_library(
-    name = "x25519",
-    srcs = [
-        "Curve25519.java",
-        "Field25519.java",
-        "X25519.java",
-    ],
-    javacopts = JAVACOPTS_OSS,
-    deps = [
-        ":subtle",
-        "//src/main/java/com/google/crypto/tink/annotations",
-    ],
-)
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/EncryptThenAuthenticate.java b/java_src/src/main/java/com/google/crypto/tink/subtle/EncryptThenAuthenticate.java
index 13911f7..b34ce04 100644
--- a/java_src/src/main/java/com/google/crypto/tink/subtle/EncryptThenAuthenticate.java
+++ b/java_src/src/main/java/com/google/crypto/tink/subtle/EncryptThenAuthenticate.java
@@ -52,7 +52,7 @@
       throws GeneralSecurityException {
     IndCpaCipher cipher = new AesCtrJceCipher(aesCtrKey, ivSize);
     SecretKeySpec hmacKeySpec = new SecretKeySpec(hmacKey, "HMAC");
-    Mac hmac = new MacJce(hmacAlgorithm, hmacKeySpec, tagSize);
+    Mac hmac = new PrfMac(new PrfHmacJce(hmacAlgorithm, hmacKeySpec), tagSize);
     return new EncryptThenAuthenticate(cipher, hmac, tagSize);
   }
 
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/MacJce.java b/java_src/src/main/java/com/google/crypto/tink/subtle/MacJce.java
deleted file mode 100644
index e842940..0000000
--- a/java_src/src/main/java/com/google/crypto/tink/subtle/MacJce.java
+++ /dev/null
@@ -1,103 +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.subtle;
-
-import com.google.crypto.tink.Mac;
-import com.google.errorprone.annotations.Immutable;
-import java.security.GeneralSecurityException;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.NoSuchAlgorithmException;
-
-/**
- * {@link Mac} implementations in JCE.
- *
- * @since 1.0.0
- */
-@Immutable
-public final class MacJce implements Mac {
-  static final int MIN_TAG_SIZE_IN_BYTES = 10;
-  static final int MIN_KEY_SIZE_IN_BYTES = 16;
-
-  @SuppressWarnings("Immutable")  // We do not mutate the underlying mac.
-  private final javax.crypto.Mac mac;
-
-  private final int digestSize;
-  private final String algorithm;
-  @SuppressWarnings("Immutable")  // We do not mutate the key.
-  private final java.security.Key key;
-
-  public MacJce(String algorithm, java.security.Key key, int digestSize)
-      throws GeneralSecurityException {
-    if (digestSize < MIN_TAG_SIZE_IN_BYTES) {
-      throw new InvalidAlgorithmParameterException(
-          "tag size too small, need at least " + MIN_TAG_SIZE_IN_BYTES + " bytes");
-    }
-    if (key.getEncoded().length < MIN_KEY_SIZE_IN_BYTES) {
-      throw new InvalidAlgorithmParameterException(
-          "key size too small, need at least " + MIN_KEY_SIZE_IN_BYTES + " bytes");
-    }
-    switch (algorithm) {
-      case "HMACSHA1":
-        if (digestSize > 20) {
-          throw new InvalidAlgorithmParameterException("tag size too big");
-        }
-        break;
-      case "HMACSHA256":
-        if (digestSize > 32) {
-          throw new InvalidAlgorithmParameterException("tag size too big");
-        }
-        break;
-      case "HMACSHA512":
-        if (digestSize > 64) {
-          throw new InvalidAlgorithmParameterException("tag size too big");
-        }
-        break;
-      default:
-        throw new NoSuchAlgorithmException("unknown Hmac algorithm: " + algorithm);
-    }
-
-    this.algorithm = algorithm;
-    this.digestSize = digestSize;
-    this.key = key;
-    this.mac = EngineFactory.MAC.getInstance(algorithm);
-    mac.init(key);
-  }
-
-  @Override
-  public byte[] computeMac(final byte[] data) throws GeneralSecurityException {
-    javax.crypto.Mac tmp;
-    try {
-      // Cloning a mac is frequently fast and thread-safe.
-      tmp = (javax.crypto.Mac) this.mac.clone();
-    } catch (java.lang.CloneNotSupportedException ex) {
-      // Unfortunately, the Mac interface in certain versions of Android is not clonable.
-      tmp = EngineFactory.MAC.getInstance(this.algorithm);
-      tmp.init(this.key);
-    }
-    tmp.update(data);
-    byte[] digest = new byte[digestSize];
-    System.arraycopy(tmp.doFinal(), 0, digest, 0, digestSize);
-    return digest;
-  }
-
-  @Override
-  public void verifyMac(final byte[] mac, final byte[] data) throws GeneralSecurityException {
-    if (!Bytes.equal(computeMac(data), mac)) {
-      throw new GeneralSecurityException("invalid MAC");
-    }
-  }
-}
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/PrfHmacJce.java b/java_src/src/main/java/com/google/crypto/tink/subtle/PrfHmacJce.java
new file mode 100644
index 0000000..8467b0a
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/subtle/PrfHmacJce.java
@@ -0,0 +1,94 @@
+// 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.subtle;
+
+import com.google.crypto.tink.prf.Prf;
+import com.google.errorprone.annotations.Immutable;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import javax.crypto.Mac;
+
+/** {@link Prf} implementation using JCE. */
+@Immutable
+public final class PrfHmacJce implements Prf {
+  static final int MIN_KEY_SIZE_IN_BYTES = 16;
+
+  // We do not mutate the underlying mac and it is bound to the containing PrfHmacJce instance.
+  @SuppressWarnings({"Immutable", "ThreadLocalUsage"})
+  private final ThreadLocal<Mac> localMac =
+      new ThreadLocal<Mac>() {
+        @Override
+        protected Mac initialValue() {
+          try {
+            Mac mac = EngineFactory.MAC.getInstance(algorithm);
+            mac.init(key);
+            return mac;
+          } catch (GeneralSecurityException ex) {
+            throw new IllegalStateException(ex);
+          }
+        }
+      };
+
+  private final String algorithm;
+  @SuppressWarnings("Immutable")  // We do not mutate the key.
+  private final java.security.Key key;
+
+  private final int maxOutputLength;
+
+  public PrfHmacJce(String algorithm, java.security.Key key) throws GeneralSecurityException {
+    this.algorithm = algorithm;
+    this.key = key;
+    if (key.getEncoded().length < MIN_KEY_SIZE_IN_BYTES) {
+      throw new InvalidAlgorithmParameterException(
+          "key size too small, need at least " + MIN_KEY_SIZE_IN_BYTES + " bytes");
+    }
+
+    switch (algorithm) {
+      case "HMACSHA1":
+        maxOutputLength = 20;
+        break;
+      case "HMACSHA256":
+        maxOutputLength = 32;
+        break;
+      case "HMACSHA512":
+        maxOutputLength = 64;
+        break;
+      default:
+        throw new NoSuchAlgorithmException("unknown Hmac algorithm: " + algorithm);
+    }
+
+    // Initialize the current threads mac, mostly to fail fast if anything is wrong.
+    localMac.get();
+  }
+
+  @Override
+  public byte[] compute(byte[] data, int outputLength) throws GeneralSecurityException {
+    if (outputLength > maxOutputLength) {
+      throw new InvalidAlgorithmParameterException("tag size too big");
+    }
+
+    localMac.get().update(data);
+    return Arrays.copyOf(localMac.get().doFinal(), outputLength);
+  }
+
+  /** Returns the maximum supported tag length. */
+  public int getMaxOutputLength() {
+    return maxOutputLength;
+  }
+}
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/PrfMac.java b/java_src/src/main/java/com/google/crypto/tink/subtle/PrfMac.java
new file mode 100644
index 0000000..c52d672
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/subtle/PrfMac.java
@@ -0,0 +1,60 @@
+// 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.subtle;
+
+import com.google.crypto.tink.Mac;
+import com.google.crypto.tink.prf.Prf;
+import com.google.errorprone.annotations.Immutable;
+import java.security.GeneralSecurityException;
+import java.security.InvalidAlgorithmParameterException;
+
+/**
+ * Class that provides the functionality expressed by the Mac primitive using a Prf implementation.
+ */
+@Immutable
+public class PrfMac implements Mac {
+  static final int MIN_TAG_SIZE_IN_BYTES = 10;
+
+  private final Prf wrappedPrf;
+  private final int tagSize;
+
+  /** Wrap {@code wrappedPrf } in a Mac primitive with the specified {@code tagSize} */
+  public PrfMac(Prf wrappedPrf, int tagSize) throws GeneralSecurityException {
+    this.wrappedPrf = wrappedPrf;
+    this.tagSize = tagSize;
+
+    // The output length is restricted by the HMAC spec. Check that first.
+    if (tagSize < MIN_TAG_SIZE_IN_BYTES) {
+      throw new InvalidAlgorithmParameterException(
+          "tag size too small, need at least " + MIN_TAG_SIZE_IN_BYTES + " bytes");
+    }
+
+    // Some Prf implementations have restrictions on maximum tag length. These throw on compute().
+    // Check for those restrictions on tag length here by doing a compute() pass.
+    wrappedPrf.compute(new byte[0], tagSize);
+  }
+
+  @Override
+  public byte[] computeMac(byte[] data) throws GeneralSecurityException {
+    return wrappedPrf.compute(data, tagSize);
+  }
+
+  @Override
+  public void verifyMac(byte[] mac, byte[] data) throws GeneralSecurityException {
+    if (!Bytes.equal(computeMac(data), mac)) {
+      throw new GeneralSecurityException("invalid MAC");
+    }
+  }
+}
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/RewindableReadableByteChannel.java b/java_src/src/main/java/com/google/crypto/tink/subtle/RewindableReadableByteChannel.java
index 59c3165..8c354bb 100644
--- a/java_src/src/main/java/com/google/crypto/tink/subtle/RewindableReadableByteChannel.java
+++ b/java_src/src/main/java/com/google/crypto/tink/subtle/RewindableReadableByteChannel.java
@@ -57,7 +57,6 @@
    * read()-calls will be forwarded directly to the wrapped
    * channel (after the currently buffered bytes are read).
    */
-  @GuardedBy("this")
   public synchronized void disableRewinding() {
     this.canRewind = false;
   }
@@ -65,7 +64,6 @@
   /**
    * Rewinds this buffer to the beginning (if rewinding is still enabled).
    */
-  @GuardedBy("this")
   public synchronized void rewind() throws IOException {
     if (!canRewind) {
       throw new IOException("Cannot rewind anymore.");
@@ -76,7 +74,6 @@
   }
 
   @Override
-  @GuardedBy("this")
   public synchronized int read(ByteBuffer dst) throws IOException {
     if (directRead) {
       return baseChannel.read(dst);
@@ -148,7 +145,6 @@
   }
 
   @Override
-  @GuardedBy("this")
   public synchronized void close() throws IOException {
     canRewind = false;
     directRead = true;
@@ -156,7 +152,6 @@
   }
 
   @Override
-  @GuardedBy("this")
   public synchronized boolean isOpen() {
     return baseChannel.isOpen();
   }
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java b/java_src/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java
index cc8f8ab..6d82bf8 100644
--- a/java_src/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java
+++ b/java_src/src/main/java/com/google/crypto/tink/subtle/SubtleUtil.java
@@ -154,7 +154,7 @@
   }
 
   /**
-   * Inserts {@param value} as unsigned into into {@param buffer}.
+   * Inserts {@code value} as unsigned into into {@code buffer}.
    *
    * <p>@throws GeneralSecurityException if not 0 <= value < 2^32.
    */
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/Validators.java b/java_src/src/main/java/com/google/crypto/tink/subtle/Validators.java
index eafc4cf..3fe8921 100644
--- a/java_src/src/main/java/com/google/crypto/tink/subtle/Validators.java
+++ b/java_src/src/main/java/com/google/crypto/tink/subtle/Validators.java
@@ -21,6 +21,7 @@
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
+import java.util.Locale;
 import java.util.regex.Pattern;
 
 /**
@@ -29,6 +30,8 @@
  * @since 1.0.0
  */
 public final class Validators {
+  private Validators() {}
+
   private static final String TYPE_URL_PREFIX = "type.googleapis.com/";
   /**
    * To reach 128-bit security strength, RSA's modulus must be at least 3072-bit while 2048-bit RSA
@@ -113,7 +116,7 @@
    */
   public static void validateNotExists(File f) throws IOException {
     if (f.exists()) {
-      throw new IOException(String.format("%s exists, please choose another file\n", f.toString()));
+      throw new IOException(String.format("%s exists, please choose another file\n", f));
     }
   }
 
@@ -121,18 +124,17 @@
   public static void validateExists(File f) throws IOException {
     if (!f.exists()) {
       throw new IOException(
-          String.format("Error: %s doesn't exist, please choose another file\n", f.toString()));
+          String.format("Error: %s doesn't exist, please choose another file\n", f));
     }
   }
 
   /**
    * Validates that {@code kmsKeyUri} starts with {@code expectedPrefix}, and removes the prefix.
    *
-   * @throws IllegalArgumentException
+   * @throws IllegalArgumentException if {@code kmsKeyUri} is invalid.
    */
-  public static String validateKmsKeyUriAndRemovePrefix(String expectedPrefix, String kmsKeyUri)
-      throws IllegalArgumentException {
-    if (!kmsKeyUri.toLowerCase().startsWith(expectedPrefix)) {
+  public static String validateKmsKeyUriAndRemovePrefix(String expectedPrefix, String kmsKeyUri) {
+    if (!kmsKeyUri.toLowerCase(Locale.US).startsWith(expectedPrefix)) {
       throw new IllegalArgumentException(
           String.format("key URI must start with %s", expectedPrefix));
     }
diff --git a/java_src/src/main/java/com/google/crypto/tink/subtle/prf/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/subtle/prf/BUILD.bazel
index d535d13..ce3c182 100644
--- a/java_src/src/main/java/com/google/crypto/tink/subtle/prf/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/subtle/prf/BUILD.bazel
@@ -2,7 +2,7 @@
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
 
 java_library(
     name = "prf",
diff --git a/java_src/src/main/java/com/google/crypto/tink/testing/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/testing/BUILD.bazel
index 084feb2..68b9276 100644
--- a/java_src/src/main/java/com/google/crypto/tink/testing/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/testing/BUILD.bazel
@@ -4,7 +4,7 @@
 
 package(
     default_testonly = 1,
-    default_visibility = ["//:__subpackages__"],
+    default_visibility = ["//visibility:public"],
 )
 
 java_library(
@@ -29,16 +29,18 @@
         "//proto:rsa_ssa_pkcs1_java_proto",
         "//proto:rsa_ssa_pss_java_proto",
         "//proto:tink_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
         "//src/main/java/com/google/crypto/tink:cleartext_keyset_handle",
-        "//src/main/java/com/google/crypto/tink:core",
-        "//src/main/java/com/google/crypto/tink:primitives",
-        "//src/main/java/com/google/crypto/tink/aead",
-        "//src/main/java/com/google/crypto/tink/daead",
-        "//src/main/java/com/google/crypto/tink/hybrid",
-        "//src/main/java/com/google/crypto/tink/mac",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink/aead:aead_config",
+        "//src/main/java/com/google/crypto/tink/daead:deterministic_aead_config",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_key_templates",
+        "//src/main/java/com/google/crypto/tink/mac:mac_config",
         "//src/main/java/com/google/crypto/tink/prf:prf_config",
-        "//src/main/java/com/google/crypto/tink/streamingaead",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_config",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:hex",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
         "@com_google_protobuf//:protobuf_javalite",
         "@maven//:com_google_truth_truth",
         "@maven//:junit_junit",
@@ -66,16 +68,18 @@
         "//proto:rsa_ssa_pkcs1_java_proto_lite",
         "//proto:rsa_ssa_pss_java_proto_lite",
         "//proto:tink_java_proto_lite",
-        "//src/main/java/com/google/crypto/tink:cleartext_keyset_handle_android",
-        "//src/main/java/com/google/crypto/tink:core-android",
-        "//src/main/java/com/google/crypto/tink:primitives",
-        "//src/main/java/com/google/crypto/tink/aead:android",
-        "//src/main/java/com/google/crypto/tink/daead:android",
-        "//src/main/java/com/google/crypto/tink/hybrid:android",
-        "//src/main/java/com/google/crypto/tink/mac:android",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:cleartext_keyset_handle-android",
+        "//src/main/java/com/google/crypto/tink:registry_cluster-android",
+        "//src/main/java/com/google/crypto/tink/aead:aead_config-android",
+        "//src/main/java/com/google/crypto/tink/daead:deterministic_aead_config-android",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_key_templates-android",
+        "//src/main/java/com/google/crypto/tink/mac:mac_config-android",
         "//src/main/java/com/google/crypto/tink/prf:prf_config-android",
-        "//src/main/java/com/google/crypto/tink/streamingaead:android",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_config-android",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:hex",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
         "@com_google_protobuf//:protobuf_javalite",
         "@maven//:com_google_truth_truth",
         "@maven//:junit_junit",
@@ -88,8 +92,8 @@
     javacopts = JAVACOPTS_OSS,
     deps = [
         ":test_util",
-        "//src/main/java/com/google/crypto/tink:primitives",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
         "@maven//:junit_junit",
     ],
 )
@@ -100,8 +104,8 @@
     javacopts = JAVACOPTS_OSS,
     deps = [
         ":test_util-android",
-        "//src/main/java/com/google/crypto/tink:primitives",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
         "@maven//:junit_junit",
     ],
 )
@@ -111,7 +115,8 @@
     srcs = ["KeyTypeManagerTestUtil.java"],
     javacopts = JAVACOPTS_OSS,
     deps = [
-        "//src/main/java/com/google/crypto/tink:core",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
         "@com_google_protobuf//:protobuf_javalite",
         "@maven//:com_google_truth_truth",
     ],
@@ -122,7 +127,8 @@
     srcs = ["KeyTypeManagerTestUtil.java"],
     javacopts = JAVACOPTS_OSS,
     deps = [
-        "//src/main/java/com/google/crypto/tink:core",
+        "//src/main/java/com/google/crypto/tink:key_template-android",
+        "//src/main/java/com/google/crypto/tink:key_type_manager-android",
         "@com_google_protobuf//:protobuf_javalite",
         "@maven//:com_google_truth_truth",
     ],
@@ -134,7 +140,8 @@
     javacopts = JAVACOPTS_OSS,
     deps = [
         ":test_util",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:enums",
         "@maven//:org_json_json",
     ],
 )
@@ -145,7 +152,8 @@
     javacopts = JAVACOPTS_OSS,
     deps = [
         ":test_util-android",
-        "//src/main/java/com/google/crypto/tink/subtle",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:enums",
         "@maven//:org_json_json",
     ],
 )
diff --git a/java_src/src/main/java/com/google/crypto/tink/util/BUILD.bazel b/java_src/src/main/java/com/google/crypto/tink/util/BUILD.bazel
index 2b94884..4b7e486 100644
--- a/java_src/src/main/java/com/google/crypto/tink/util/BUILD.bazel
+++ b/java_src/src/main/java/com/google/crypto/tink/util/BUILD.bazel
@@ -1,8 +1,36 @@
+load("@build_bazel_rules_android//android:rules.bzl", "android_library")
 load("//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
 
 licenses(["notice"])
 
-package(default_visibility = ["//:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+    name = "keys_downloader",
+    srcs = ["KeysDownloader.java"],
+    deps = [
+        "@maven//:com_google_code_findbugs_jsr305",
+        "@maven//:com_google_http_client_google_http_client",
+        "@maven//:joda_time_joda_time",
+    ],
+)
+
+java_library(
+    name = "test_util",
+    srcs = ["TestUtil.java"],
+)
+
+android_library(
+    name = "keys_downloader-android",
+    srcs = ["KeysDownloader.java"],
+    deps = [
+        "@maven//:com_google_code_findbugs_jsr305",
+        "@maven//:com_google_http_client_google_http_client",
+        "@maven//:joda_time_joda_time",
+    ],
+)
+
+# Deprecated, will be removed.
 
 filegroup(
     name = "srcs",
diff --git a/java_src/src/main/java/com/google/crypto/tink/util/TestUtil.java b/java_src/src/main/java/com/google/crypto/tink/util/TestUtil.java
new file mode 100644
index 0000000..26e66b4
--- /dev/null
+++ b/java_src/src/main/java/com/google/crypto/tink/util/TestUtil.java
@@ -0,0 +1,143 @@
+// 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.util;
+
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+
+/** Provides various utility methods for testing. */
+public class TestUtil {
+
+  private TestUtil() {}
+
+  /**
+   * Uses a z test on the given byte string, expecting all bits to be uniformly set with probability
+   * 1/2. Returns non ok status if the z test fails by more than 10 standard deviations.
+   *
+   * <p>With less statistics jargon: This counts the number of bits set and expects the number to be
+   * roughly half of the length of the string. The law of large numbers suggests that we can assume
+   * that the longer the string is, the more accurate that estimate becomes for a random string.
+   * This test is useful to detect things like strings that are entirely zero.
+   *
+   * <p>Note: By itself, this is a very weak test for randomness.
+   *
+   * @throws GeneralSecurityException if uniformity error is detected, otherwise returns normally.
+   */
+  public static void ztestUniformString(byte[] string) throws GeneralSecurityException {
+    final double minAcceptableStdDevs = 10.0;
+    double totalBits = string.length * 8;
+    double expected = totalBits / 2.0;
+    double stddev = Math.sqrt(totalBits / 4.0);
+
+    // This test is very limited at low string lengths. Below a certain threshold it tests nothing.
+    if (expected < stddev * minAcceptableStdDevs) {
+      throw new GeneralSecurityException(
+          "Test will always succeed with strings of the given length "
+              + string.length
+              + ". Use more bytes.");
+    }
+
+    long numSetBits = 0;
+    for (byte b : string) {
+      int unsignedInt = toUnsignedInt(b);
+      // Counting the number of bits set in byte:
+      while (unsignedInt != 0) {
+        numSetBits++;
+        unsignedInt = (unsignedInt & (unsignedInt - 1));
+      }
+    }
+    // Check that the number of bits is within 10 stddevs.
+    if (Math.abs((double) numSetBits - expected) < minAcceptableStdDevs * stddev) {
+      return;
+    }
+    throw new GeneralSecurityException(
+        "Z test for uniformly distributed variable out of bounds; "
+            + "Actual number of set bits was "
+            + numSetBits
+            + " expected was "
+            + expected
+            + " 10 * standard deviation is 10 * "
+            + stddev
+            + " = "
+            + 10.0 * stddev);
+  }
+
+  /**
+   * Tests that the crosscorrelation of two strings of equal length points to independent and
+   * uniformly distributed strings. Returns non ok status if the z test fails by more than 10
+   * standard deviations.
+   *
+   * <p>With less statistics jargon: This xors two strings and then performs the ZTestUniformString
+   * on the result. If the two strings are independent and uniformly distributed, the xor'ed string
+   * is as well. A cross correlation test will find whether two strings overlap more or less than it
+   * would be expected.
+   *
+   * <p>Note: Having a correlation of zero is only a necessary but not sufficient condition for
+   * independence.
+   *
+   * @throws GeneralSecurityException if uniformity error is detected, otherwise returns normally.
+   */
+  public static void ztestCrossCorrelationUniformStrings(byte[] string1, byte[] string2)
+      throws GeneralSecurityException {
+    if (string1.length != string2.length) {
+      throw new GeneralSecurityException("Strings are not of equal length");
+    }
+    byte[] crossed = new byte[string1.length];
+    for (int i = 0; i < string1.length; i++) {
+      crossed[i] = (byte) (string1[i] ^ string2[i]);
+    }
+    ztestUniformString(crossed);
+  }
+
+  /**
+   * Tests that the autocorrelation of a string points to the bits being independent and uniformly
+   * distributed. Rotates the string in a cyclic fashion. Returns non ok status if the z test fails
+   * by more than 10 standard deviations.
+   *
+   * <p>With less statistics jargon: This rotates the string bit by bit and performs
+   * ZTestCrosscorrelationUniformStrings on each of the rotated strings and the original. This will
+   * find self similarity of the input string, especially periodic self similarity. For example, it
+   * is a decent test to find English text (needs about 180 characters with the current settings).
+   *
+   * <p>Note: Having a correlation of zero is only a necessary but not sufficient condition for
+   * independence.
+   *
+   * @throws GeneralSecurityException if uniformity error is detected, otherwise returns normally.
+   */
+  public static void ztestAutocorrelationUniformString(byte[] string)
+      throws GeneralSecurityException {
+    byte[] rotated = Arrays.copyOf(string, string.length);
+
+    for (int i = 1; i < string.length * 8; i++) {
+      rotate(rotated);
+      ztestCrossCorrelationUniformStrings(string, rotated);
+    }
+  }
+
+  /** Manual implementation of Byte.toUnsignedByte. The Android JDK does not have this method. */
+  private static int toUnsignedInt(byte b) {
+    return b & 0xff;
+  }
+
+  private static void rotate(byte[] string) {
+    byte[] ref = Arrays.copyOf(string, string.length);
+    for (int i = 0; i < string.length; i++) {
+      string[i] =
+          (byte)
+              ((toUnsignedInt(string[i]) >> 1)
+                  | ((1 & toUnsignedInt(ref[(i == 0 ? string.length : i) - 1])) << 7));
+    }
+  }
+}
diff --git a/java_src/src/main/resources/META-INF/proguard/protobuf.pro b/java_src/src/main/resources/META-INF/proguard/protobuf.pro
new file mode 100644
index 0000000..b2508fc
--- /dev/null
+++ b/java_src/src/main/resources/META-INF/proguard/protobuf.pro
@@ -0,0 +1,14 @@
+# Recently Protobuf Javalite introduced a change that relies on reflection,
+# which doesn't work with Proguard. This rule keeps the reflection usages in
+# (shaded) Protobuf classes in Tink as-is.
+# The location of this file is determined by
+# - https://developer.android.com/studio/build/shrink-code#configuration-files
+# - https://docs.bazel.build/versions/master/be/java.html#java_library.resources
+# See also:
+# - https://github.com/google/tink/issues/361
+# - https://github.com/protocolbuffers/protobuf/issues/6463
+# WARNING: the shaded package name com.google.crypto.tink.shaded.protobuf must
+# be kept in sync with jar_jar_rules.txt.
+-keepclassmembers class * extends com.google.crypto.tink.shaded.protobuf.GeneratedMessageLite {
+  <fields>;
+}
diff --git a/java_src/src/test/BUILD.bazel b/java_src/src/test/BUILD.bazel
index df4c0b1..71ecef0 100644
--- a/java_src/src/test/BUILD.bazel
+++ b/java_src/src/test/BUILD.bazel
@@ -9,19 +9,183 @@
         "**/*.java",
     ]),
     deps = [
-        "//:awskms",
-        "//:gcpkms",
-        "//:testonly",
+        "//proto:aes_cmac_java_proto",
+        "//proto:aes_ctr_hmac_aead_java_proto",
+        "//proto:aes_ctr_hmac_streaming_java_proto",
+        "//proto:aes_ctr_java_proto",
+        "//proto:aes_eax_java_proto",
+        "//proto:aes_gcm_hkdf_streaming_java_proto",
+        "//proto:aes_gcm_java_proto",
+        "//proto:aes_siv_java_proto",
+        "//proto:chacha20_poly1305_java_proto",
+        "//proto:common_java_proto",
+        "//proto:config_java_proto",
+        "//proto:ecdsa_java_proto",
+        "//proto:ecies_aead_hkdf_java_proto",
+        "//proto:ed25519_java_proto",
+        "//proto:hkdf_prf_java_proto",
+        "//proto:hmac_java_proto",
+        "//proto:kms_aead_java_proto",
+        "//proto:kms_envelope_java_proto",
+        "//proto:prf_based_deriver_java_proto",
+        "//proto:rsa_ssa_pkcs1_java_proto",
+        "//proto:rsa_ssa_pss_java_proto",
+        "//proto:tink_java_proto",
+        "//proto:xchacha20_poly1305_java_proto",
+        "//src/main/java/com/google/crypto/tink:aead",
+        "//src/main/java/com/google/crypto/tink:binary_keyset_reader",
+        "//src/main/java/com/google/crypto/tink:binary_keyset_writer",
+        "//src/main/java/com/google/crypto/tink:catalogue",
+        "//src/main/java/com/google/crypto/tink:cleartext_keyset_handle",
+        "//src/main/java/com/google/crypto/tink:config",
+        "//src/main/java/com/google/crypto/tink:core",
+        "//src/main/java/com/google/crypto/tink:crypto_format",
+        "//src/main/java/com/google/crypto/tink:deterministic_aead",
+        "//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink:json_keyset_reader",
+        "//src/main/java/com/google/crypto/tink:json_keyset_writer",
+        "//src/main/java/com/google/crypto/tink:key_manager",
+        "//src/main/java/com/google/crypto/tink:key_manager_impl",
+        "//src/main/java/com/google/crypto/tink:key_template",
+        "//src/main/java/com/google/crypto/tink:key_type_manager",
+        "//src/main/java/com/google/crypto/tink:key_wrap",
+        "//src/main/java/com/google/crypto/tink:keyset_reader",
+        "//src/main/java/com/google/crypto/tink:keyset_writer",
+        "//src/main/java/com/google/crypto/tink:kms_client",
+        "//src/main/java/com/google/crypto/tink:kms_clients",
+        "//src/main/java/com/google/crypto/tink:mac",
+        "//src/main/java/com/google/crypto/tink:no_secret_keyset_handle",
+        "//src/main/java/com/google/crypto/tink:primitive_set",
+        "//src/main/java/com/google/crypto/tink:primitive_wrapper",
+        "//src/main/java/com/google/crypto/tink:private_key_manager",
+        "//src/main/java/com/google/crypto/tink:private_key_manager_impl",
+        "//src/main/java/com/google/crypto/tink:private_key_type_manager",
+        "//src/main/java/com/google/crypto/tink:public_key_sign",
+        "//src/main/java/com/google/crypto/tink:public_key_verify",
+        "//src/main/java/com/google/crypto/tink:registry_cluster",
+        "//src/main/java/com/google/crypto/tink:streaming_aead",
+        "//src/main/java/com/google/crypto/tink:util",
+        "//src/main/java/com/google/crypto/tink/aead:aead_config",
+        "//src/main/java/com/google/crypto/tink/aead:aead_key_templates",
+        "//src/main/java/com/google/crypto/tink/aead:aead_wrapper",
+        "//src/main/java/com/google/crypto/tink/aead:aes_ctr_hmac_aead_key_manager",
+        "//src/main/java/com/google/crypto/tink/aead:aes_ctr_key_manager",
+        "//src/main/java/com/google/crypto/tink/aead:aes_eax_key_manager",
+        "//src/main/java/com/google/crypto/tink/aead:aes_gcm_key_manager",
+        "//src/main/java/com/google/crypto/tink/aead:cha_cha20_poly1305_key_manager",
+        "//src/main/java/com/google/crypto/tink/aead:kms_aead_key_manager",
+        "//src/main/java/com/google/crypto/tink/aead:kms_envelope_aead_key_manager",
+        "//src/main/java/com/google/crypto/tink/aead:x_cha_cha20_poly1305_key_manager",
+        "//src/main/java/com/google/crypto/tink/aead/subtle:aead_factory",
+        "//src/main/java/com/google/crypto/tink/aead/subtle:aes_gcm_factory",
+        "//src/main/java/com/google/crypto/tink/config:tink_config",
+        "//src/main/java/com/google/crypto/tink/daead:aes_siv_key_manager",
+        "//src/main/java/com/google/crypto/tink/daead:deterministic_aead_config",
+        "//src/main/java/com/google/crypto/tink/daead:deterministic_aead_factory",
+        "//src/main/java/com/google/crypto/tink/daead:deterministic_aead_key_templates",
+        "//src/main/java/com/google/crypto/tink/daead:deterministic_aead_wrapper",
+        "//src/main/java/com/google/crypto/tink/hybrid:ecies_aead_hkdf_private_key_manager",
+        "//src/main/java/com/google/crypto/tink/hybrid:ecies_aead_hkdf_public_key_manager",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_config",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_decrypt_factory",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_decrypt_wrapper",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_encrypt_factory",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_encrypt_wrapper",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_key_templates",
+        "//src/main/java/com/google/crypto/tink/hybrid:hybrid_util",
+        "//src/main/java/com/google/crypto/tink/hybrid:registry_ecies_aead_hkdf_dem_helper",
+        "//src/main/java/com/google/crypto/tink/hybrid/subtle:rsa_kem",
+        "//src/main/java/com/google/crypto/tink/hybrid/subtle:rsa_kem_hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink/hybrid/subtle:rsa_kem_hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink/integration/awskms:aws_kms_aead",
+        "//src/main/java/com/google/crypto/tink/integration/gcpkms:gcp_kms_client",
+        "//src/main/java/com/google/crypto/tink/mac:aes_cmac_key_manager",
+        "//src/main/java/com/google/crypto/tink/mac:hmac_key_manager",
+        "//src/main/java/com/google/crypto/tink/mac:mac_config",
+        "//src/main/java/com/google/crypto/tink/mac:mac_factory",
+        "//src/main/java/com/google/crypto/tink/mac:mac_key_templates",
+        "//src/main/java/com/google/crypto/tink/mac:mac_wrapper",
         "//src/main/java/com/google/crypto/tink/prf:hkdf_prf_key_manager",
         "//src/main/java/com/google/crypto/tink/prf:prf_config",
         "//src/main/java/com/google/crypto/tink/prf:prf_key_templates",
         "//src/main/java/com/google/crypto/tink/prf:prf_set",
         "//src/main/java/com/google/crypto/tink/prf:prf_set_wrapper",
+        "//src/main/java/com/google/crypto/tink/signature:ecdsa_sign_key_manager",
+        "//src/main/java/com/google/crypto/tink/signature:ecdsa_verify_key_manager",
+        "//src/main/java/com/google/crypto/tink/signature:ed25519_private_key_manager",
+        "//src/main/java/com/google/crypto/tink/signature:ed25519_public_key_manager",
+        "//src/main/java/com/google/crypto/tink/signature:public_key_sign_factory",
+        "//src/main/java/com/google/crypto/tink/signature:public_key_sign_wrapper",
+        "//src/main/java/com/google/crypto/tink/signature:public_key_verify_factory",
+        "//src/main/java/com/google/crypto/tink/signature:public_key_verify_wrapper",
+        "//src/main/java/com/google/crypto/tink/signature:rsa_ssa_pkcs1_sign_key_manager",
+        "//src/main/java/com/google/crypto/tink/signature:rsa_ssa_pkcs1_verify_key_manager",
+        "//src/main/java/com/google/crypto/tink/signature:rsa_ssa_pss_sign_key_manager",
+        "//src/main/java/com/google/crypto/tink/signature:rsa_ssa_pss_verify_key_manager",
+        "//src/main/java/com/google/crypto/tink/signature:sig_util",
+        "//src/main/java/com/google/crypto/tink/signature:signature_config",
+        "//src/main/java/com/google/crypto/tink/signature:signature_key_templates",
+        "//src/main/java/com/google/crypto/tink/signature:signature_pem_keyset_reader",
+        "//src/main/java/com/google/crypto/tink/streamingaead:aes_ctr_hmac_streaming_key_manager",
+        "//src/main/java/com/google/crypto/tink/streamingaead:aes_gcm_hkdf_streaming_key_manager",
+        "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_config",
+        "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_factory",
+        "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_key_templates",
+        "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_util",
+        "//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_wrapper",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_cmac",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_ctr_hmac_streaming",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_ctr_jce_cipher",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_eax_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_gcm_hkdf_streaming",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_gcm_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_siv",
+        "//src/main/java/com/google/crypto/tink/subtle:aes_util",
+        "//src/main/java/com/google/crypto/tink/subtle:bytes",
+        "//src/main/java/com/google/crypto/tink/subtle:cha_cha20",
+        "//src/main/java/com/google/crypto/tink/subtle:cha_cha20_poly1305",
+        "//src/main/java/com/google/crypto/tink/subtle:ecdsa_sign_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:ecdsa_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_dem_helper",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_hybrid_decrypt",
+        "//src/main/java/com/google/crypto/tink/subtle:ecies_aead_hkdf_hybrid_encrypt",
+        "//src/main/java/com/google/crypto/tink/subtle:ed25519_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:ed25519_sign",
+        "//src/main/java/com/google/crypto/tink/subtle:ed25519_verify",
+        "//src/main/java/com/google/crypto/tink/subtle:elliptic_curves",
+        "//src/main/java/com/google/crypto/tink/subtle:encrypt_then_authenticate",
+        "//src/main/java/com/google/crypto/tink/subtle:enums",
+        "//src/main/java/com/google/crypto/tink/subtle:field25519",
+        "//src/main/java/com/google/crypto/tink/subtle:hex",
+        "//src/main/java/com/google/crypto/tink/subtle:hkdf",
+        "//src/main/java/com/google/crypto/tink/subtle:immutable_byte_array",
+        "//src/main/java/com/google/crypto/tink/subtle:ind_cpa_cipher",
+        "//src/main/java/com/google/crypto/tink/subtle:kwp",
+        "//src/main/java/com/google/crypto/tink/subtle:pem_key_type",
+        "//src/main/java/com/google/crypto/tink/subtle:poly1305",
+        "//src/main/java/com/google/crypto/tink/subtle:prf_hmac_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:prf_mac",
+        "//src/main/java/com/google/crypto/tink/subtle:random",
+        "//src/main/java/com/google/crypto/tink/subtle:rewindable_readable_byte_channel",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pkcs1_sign_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pkcs1_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pss_sign_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:rsa_ssa_pss_verify_jce",
+        "//src/main/java/com/google/crypto/tink/subtle:subtle_util_cluster",
+        "//src/main/java/com/google/crypto/tink/subtle:validators",
+        "//src/main/java/com/google/crypto/tink/subtle:x25519",
+        "//src/main/java/com/google/crypto/tink/subtle:x_cha_cha20",
+        "//src/main/java/com/google/crypto/tink/subtle:x_cha_cha20_poly1305",
+        "//src/main/java/com/google/crypto/tink/subtle/prf:hkdf_streaming_prf",
         "//src/main/java/com/google/crypto/tink/subtle/prf:prf_impl",
+        "//src/main/java/com/google/crypto/tink/subtle/prf:streaming_prf",
         "//src/main/java/com/google/crypto/tink/testing:key_type_manager_test_util",
         "//src/main/java/com/google/crypto/tink/testing:streaming_test_util",
         "//src/main/java/com/google/crypto/tink/testing:test_util",
         "//src/main/java/com/google/crypto/tink/testing:wycheproof_test_util",
+        "//src/main/java/com/google/crypto/tink/util:keys_downloader",
+        "//src/main/java/com/google/crypto/tink/util:test_util",
         "@com_google_protobuf//:protobuf_javalite",
         "@maven//:com_amazonaws_aws_java_sdk_core",
         "@maven//:com_amazonaws_aws_java_sdk_kms",
diff --git a/java_src/src/test/java/com/google/crypto/tink/KeysetHandleTest.java b/java_src/src/test/java/com/google/crypto/tink/KeysetHandleTest.java
index f26902f..0b5bc66 100644
--- a/java_src/src/test/java/com/google/crypto/tink/KeysetHandleTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/KeysetHandleTest.java
@@ -32,6 +32,7 @@
 import com.google.crypto.tink.proto.AesGcmKeyFormat;
 import com.google.crypto.tink.proto.EcdsaPrivateKey;
 import com.google.crypto.tink.proto.KeyData;
+import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
 import com.google.crypto.tink.proto.KeyStatusType;
 import com.google.crypto.tink.proto.Keyset;
 import com.google.crypto.tink.proto.OutputPrefixType;
@@ -42,7 +43,9 @@
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.testing.TestUtil;
 import com.google.crypto.tink.testing.TestUtil.DummyAead;
+import com.google.protobuf.ByteString;
 import com.google.protobuf.ExtensionRegistryLite;
+import com.google.protobuf.InvalidProtocolBufferException;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.security.GeneralSecurityException;
@@ -56,6 +59,39 @@
 /** Tests for KeysetHandle. */
 @RunWith(JUnit4.class)
 public class KeysetHandleTest {
+  /**
+   * A KeyTypeManager for testing. It accepts AesGcmKeys and produces primitives as with the passed
+   * in factory.
+   */
+  public static class TestKeyTypeManager extends KeyTypeManager<AesGcmKey> {
+    public TestKeyTypeManager(PrimitiveFactory<?, AesGcmKey>... factories) {
+      super(AesGcmKey.class, factories);
+    }
+
+    @Override
+    public String getKeyType() {
+      return "type.googleapis.com/google.crypto.tink.AesGcmKey";
+    }
+
+    @Override
+    public int getVersion() {
+      return 1;
+    }
+
+    @Override
+    public KeyMaterialType keyMaterialType() {
+      return KeyMaterialType.SYMMETRIC;
+    }
+
+    @Override
+    public void validateKey(AesGcmKey keyProto) {}
+
+    @Override
+    public AesGcmKey parseKey(ByteString byteString) throws InvalidProtocolBufferException {
+      return AesGcmKey.parseFrom(byteString, ExtensionRegistryLite.getEmptyRegistry());
+    }
+  }
+
   @BeforeClass
   public static void setUp() throws GeneralSecurityException {
     Config.register(TinkConfig.TINK_1_0_0);
@@ -287,9 +323,8 @@
     // always fails.
     KeyManager<Aead> manager =
         new KeyManagerImpl<>(
-            new KeyTypeManagerTest.TestKeyTypeManager(
-                new KeyTypeManagerTest.TestKeyTypeManager.PrimitiveFactory<Aead, AesGcmKey>(
-                    Aead.class) {
+            new TestKeyTypeManager(
+                new KeyTypeManager.PrimitiveFactory<Aead, AesGcmKey>(Aead.class) {
                   @Override
                   public Aead getPrimitive(AesGcmKey key) {
                     return new DummyAead();
diff --git a/java_src/src/test/java/com/google/crypto/tink/RegistryTest.java b/java_src/src/test/java/com/google/crypto/tink/RegistryTest.java
index 53ad42d..3891a27 100644
--- a/java_src/src/test/java/com/google/crypto/tink/RegistryTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/RegistryTest.java
@@ -44,7 +44,7 @@
 import com.google.crypto.tink.subtle.AesEaxJce;
 import com.google.crypto.tink.subtle.AesGcmJce;
 import com.google.crypto.tink.subtle.EncryptThenAuthenticate;
-import com.google.crypto.tink.subtle.MacJce;
+import com.google.crypto.tink.subtle.PrfMac;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.testing.TestUtil.DummyAead;
 import com.google.protobuf.ByteString;
@@ -158,7 +158,7 @@
       fail("Expected ClassCastException");
     } catch (ClassCastException e) {
       assertExceptionContains(e, "com.google.crypto.tink.Aead");
-      assertExceptionContains(e, "com.google.crypto.tink.subtle.MacJce");
+      assertExceptionContains(e, "com.google.crypto.tink.subtle.PrfMac");
     }
   }
 
@@ -386,7 +386,7 @@
     assertThat(hmacKey.getParams().getHash()).isEqualTo(HashType.SHA256);
     assertThat(hmacKeyData.getTypeUrl()).isEqualTo(MacConfig.HMAC_TYPE_URL);
     // This might break when we add native implementations.
-    assertThat(mac.getClass()).isEqualTo(MacJce.class);
+    assertThat(mac.getClass()).isEqualTo(PrfMac.class);
   }
 
   @Test
@@ -401,7 +401,7 @@
     assertThat(hmacKey.getParams().getHash()).isEqualTo(HashType.SHA256);
     assertThat(hmacKeyData.getTypeUrl()).isEqualTo(MacConfig.HMAC_TYPE_URL);
     // This might break when we add native implementations.
-    assertThat(mac.getClass()).isEqualTo(MacJce.class);
+    assertThat(mac.getClass()).isEqualTo(PrfMac.class);
   }
 
   @Test
diff --git a/java_src/src/test/java/com/google/crypto/tink/daead/AesSivKeyManagerTest.java b/java_src/src/test/java/com/google/crypto/tink/daead/AesSivKeyManagerTest.java
index cdbedbf..5263cd5 100644
--- a/java_src/src/test/java/com/google/crypto/tink/daead/AesSivKeyManagerTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/daead/AesSivKeyManagerTest.java
@@ -21,6 +21,7 @@
 
 import com.google.crypto.tink.DeterministicAead;
 import com.google.crypto.tink.KeyTemplate;
+import com.google.crypto.tink.KeyTypeManager;
 import com.google.crypto.tink.proto.AesSivKey;
 import com.google.crypto.tink.proto.AesSivKeyFormat;
 import com.google.crypto.tink.proto.KeyData.KeyMaterialType;
@@ -130,7 +131,7 @@
   public void createKey_multipleCallsCreateDifferentKeys() throws Exception {
     AesSivKeyFormat format = createAesSivKeyFormat(64);
     TreeSet<String> keys = new TreeSet<>();
-    AesSivKeyManager.KeyFactory<AesSivKeyFormat, AesSivKey> factory =
+    KeyTypeManager.KeyFactory<AesSivKeyFormat, AesSivKey> factory =
         new AesSivKeyManager().keyFactory();
     final int numKeys = 1000;
     for (int i = 0; i < numKeys; ++i) {
diff --git a/java_src/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridDecryptTest.java b/java_src/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridDecryptTest.java
index d9bb7bf..d5ff72e 100644
--- a/java_src/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridDecryptTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/hybrid/EciesAeadHkdfHybridDecryptTest.java
@@ -17,7 +17,7 @@
 package com.google.crypto.tink.hybrid;
 
 import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertThrows;
 
 import com.google.crypto.tink.Config;
 import com.google.crypto.tink.HybridDecrypt;
@@ -29,7 +29,6 @@
 import com.google.crypto.tink.subtle.EciesAeadHkdfHybridEncrypt;
 import com.google.crypto.tink.subtle.EllipticCurves;
 import com.google.crypto.tink.subtle.EllipticCurves.CurveType;
-import com.google.crypto.tink.subtle.Hex;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.testing.TestUtil;
 import com.google.crypto.tink.testing.TestUtil.BytesMutation;
@@ -51,14 +50,15 @@
     Config.register(HybridConfig.TINK_1_0_0);
   }
 
-  private void testModifyDecrypt(CurveType curveType, KeyTemplate keyTemplate) throws Exception {
+  private static void testEncryptDecrypt(CurveType curveType, KeyTemplate keyTemplate)
+      throws Exception {
     KeyPair recipientKey = EllipticCurves.generateKeyPair(curveType);
     ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
     ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
     byte[] salt = Random.randBytes(8);
+    String hmacAlgo = HybridUtil.toHmacAlgo(HashType.SHA256);
     byte[] plaintext = Random.randBytes(4);
     byte[] context = Random.randBytes(4);
-    String hmacAlgo = HybridUtil.toHmacAlgo(HashType.SHA256);
     HybridEncrypt hybridEncrypt =
         new EciesAeadHkdfHybridEncrypt(
             recipientPublicKey,
@@ -75,70 +75,241 @@
             new RegistryEciesAeadHkdfDemHelper(keyTemplate));
     byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
     byte[] decrypted = hybridDecrypt.decrypt(ciphertext, context);
-
     assertArrayEquals(plaintext, decrypted);
+  }
 
-    // Modifies ciphertext and makes sure that the decryption failed. This test implicitly checks
-    // the modification of public key and the raw ciphertext.
+  @Test
+  public void testEncryptDecryptP256CtrHmac() throws Exception {
+    testEncryptDecrypt(CurveType.NIST_P256, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP384CtrHmac() throws Exception {
+    testEncryptDecrypt(CurveType.NIST_P384, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP521CtrHmac() throws Exception {
+    testEncryptDecrypt(CurveType.NIST_P521, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP256Gcm() throws Exception {
+    testEncryptDecrypt(CurveType.NIST_P256, AeadKeyTemplates.AES128_GCM);
+  }
+
+  @Test
+  public void testEncryptDecryptP384Gcm() throws Exception {
+    testEncryptDecrypt(CurveType.NIST_P384, AeadKeyTemplates.AES128_GCM);
+  }
+
+  @Test
+  public void testEncryptDecryptP512Gcm() throws Exception {
+    testEncryptDecrypt(CurveType.NIST_P521, AeadKeyTemplates.AES128_GCM);
+  }
+
+  private static void testEncryptDecrypt_mutatedCiphertext_throws(
+      CurveType curveType, KeyTemplate keyTemplate) throws Exception {
+    KeyPair recipientKey = EllipticCurves.generateKeyPair(curveType);
+    ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
+    ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
+    byte[] salt = Random.randBytes(8);
+    String hmacAlgo = HybridUtil.toHmacAlgo(HashType.SHA256);
+    byte[] plaintext = Random.randBytes(4);
+    byte[] context = Random.randBytes(4);
+    HybridEncrypt hybridEncrypt =
+        new EciesAeadHkdfHybridEncrypt(
+            recipientPublicKey,
+            salt,
+            hmacAlgo,
+            EllipticCurves.PointFormatType.UNCOMPRESSED,
+            new RegistryEciesAeadHkdfDemHelper(keyTemplate));
+    HybridDecrypt hybridDecrypt =
+        new EciesAeadHkdfHybridDecrypt(
+            recipientPrivateKey,
+            salt,
+            hmacAlgo,
+            EllipticCurves.PointFormatType.UNCOMPRESSED,
+            new RegistryEciesAeadHkdfDemHelper(keyTemplate));
+    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
     for (BytesMutation mutation : TestUtil.generateMutations(ciphertext)) {
-      try {
-        hybridDecrypt.decrypt(mutation.value, context);
-        fail(
-            String.format(
-                "Invalid ciphertext, should have thrown exception: ciphertext = %s,context = %s,"
-                    + " description = %s",
-                Hex.encode(mutation.value), Hex.encode(context), mutation.description));
-      } catch (GeneralSecurityException expected) {
-        // Expected
+      assertThrows(
+          GeneralSecurityException.class, () -> hybridDecrypt.decrypt(mutation.value, context));
+      // The test takes too long in TSan, so we stop after the first case.
+      if (TestUtil.isTsan()) {
+        return;
       }
     }
+  }
 
-    // Modify context.
+  @Test
+  public void testEncryptDecryptP256CtrHmac_mutatedCiphertext_throws() throws Exception {
+    testEncryptDecrypt_mutatedCiphertext_throws(
+        CurveType.NIST_P256, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP384CtrHmac_mutatedCiphertext_throws() throws Exception {
+    testEncryptDecrypt_mutatedCiphertext_throws(
+        CurveType.NIST_P384, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP521CtrHmac_mutatedCiphertext_throws() throws Exception {
+    testEncryptDecrypt_mutatedCiphertext_throws(
+        CurveType.NIST_P521, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP256Gcm_mutatedCiphertext_throws() throws Exception {
+    testEncryptDecrypt_mutatedCiphertext_throws(CurveType.NIST_P256, AeadKeyTemplates.AES128_GCM);
+  }
+
+  @Test
+  public void testEncryptDecryptP384Gcm_mutatedCiphertext_throws() throws Exception {
+    testEncryptDecrypt_mutatedCiphertext_throws(CurveType.NIST_P384, AeadKeyTemplates.AES128_GCM);
+  }
+
+  @Test
+  public void testEncryptDecryptP512Gcm_mutatedCiphertext_throws() throws Exception {
+    testEncryptDecrypt_mutatedCiphertext_throws(CurveType.NIST_P521, AeadKeyTemplates.AES128_GCM);
+  }
+
+  private static void testEncryptDecrypt_mutatedContext_throws(
+      CurveType curveType, KeyTemplate keyTemplate) throws Exception {
+    KeyPair recipientKey = EllipticCurves.generateKeyPair(curveType);
+    ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
+    ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
+    byte[] salt = Random.randBytes(8);
+    String hmacAlgo = HybridUtil.toHmacAlgo(HashType.SHA256);
+    byte[] plaintext = Random.randBytes(4);
+    byte[] context = Random.randBytes(4);
+    HybridEncrypt hybridEncrypt =
+        new EciesAeadHkdfHybridEncrypt(
+            recipientPublicKey,
+            salt,
+            hmacAlgo,
+            EllipticCurves.PointFormatType.UNCOMPRESSED,
+            new RegistryEciesAeadHkdfDemHelper(keyTemplate));
+    HybridDecrypt hybridDecrypt =
+        new EciesAeadHkdfHybridDecrypt(
+            recipientPrivateKey,
+            salt,
+            hmacAlgo,
+            EllipticCurves.PointFormatType.UNCOMPRESSED,
+            new RegistryEciesAeadHkdfDemHelper(keyTemplate));
+    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
     for (BytesMutation mutation : TestUtil.generateMutations(context)) {
-      try {
-        hybridDecrypt.decrypt(ciphertext, mutation.value);
-        fail(
-            String.format(
-                "Invalid context, should have thrown exception: context = %s, ciphertext = %s,"
-                    + " description = %s",
-                Hex.encode(mutation.value), Hex.encode(ciphertext), mutation.description));
-      } catch (GeneralSecurityException expected) {
-        // Expected
+      // The test takes too long in TSan, so we stop after the first case.
+      assertThrows(
+          GeneralSecurityException.class, () -> hybridDecrypt.decrypt(ciphertext, mutation.value));
+      if (TestUtil.isTsan()) {
+        return;
       }
     }
+  }
 
-    // Modify salt.
-    // We exclude tests that modify the length of the salt, since the salt has fixed length and
-    // modifying the length may not be detected.
+  @Test
+  public void testEncryptDecryptP256CtrHmac_mutatedContext_throws() throws Exception {
+    testEncryptDecrypt_mutatedContext_throws(
+        CurveType.NIST_P256, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP384CtrHmac_mutatedContext_throws() throws Exception {
+    testEncryptDecrypt_mutatedContext_throws(
+        CurveType.NIST_P384, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP521CtrHmac_mutatedContext_throws() throws Exception {
+    testEncryptDecrypt_mutatedContext_throws(CurveType.NIST_P521, AeadKeyTemplates.AES128_GCM);
+  }
+
+  @Test
+  public void testEncryptDecryptP256Gcm_mutatedContext_throws() throws Exception {
+    testEncryptDecrypt_mutatedContext_throws(CurveType.NIST_P256, AeadKeyTemplates.AES128_GCM);
+  }
+
+  @Test
+  public void testEncryptDecryptP384Gcm_mutatedContext_throws() throws Exception {
+    testEncryptDecrypt_mutatedContext_throws(CurveType.NIST_P384, AeadKeyTemplates.AES128_GCM);
+  }
+
+  @Test
+  public void testEncryptDecryptP512Gcm_mutatedContext_throws() throws Exception {
+    testEncryptDecrypt_mutatedContext_throws(CurveType.NIST_P521, AeadKeyTemplates.AES128_GCM);
+  }
+
+  private static void testEncryptDecrypt_mutatedSalt_throws(
+      CurveType curveType, KeyTemplate keyTemplate) throws Exception {
+    KeyPair recipientKey = EllipticCurves.generateKeyPair(curveType);
+    ECPublicKey recipientPublicKey = (ECPublicKey) recipientKey.getPublic();
+    ECPrivateKey recipientPrivateKey = (ECPrivateKey) recipientKey.getPrivate();
+    byte[] salt = Random.randBytes(8);
+    String hmacAlgo = HybridUtil.toHmacAlgo(HashType.SHA256);
+    byte[] plaintext = Random.randBytes(4);
+    byte[] context = Random.randBytes(4);
+    HybridEncrypt hybridEncrypt =
+        new EciesAeadHkdfHybridEncrypt(
+            recipientPublicKey,
+            salt,
+            hmacAlgo,
+            EllipticCurves.PointFormatType.UNCOMPRESSED,
+            new RegistryEciesAeadHkdfDemHelper(keyTemplate));
+    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
+
     for (int bytes = 0; bytes < salt.length; bytes++) {
       for (int bit = 0; bit < 8; bit++) {
         byte[] modifiedSalt = Arrays.copyOf(salt, salt.length);
         modifiedSalt[bytes] ^= (byte) (1 << bit);
-        hybridDecrypt =
+        HybridDecrypt hybridDecrypt =
             new EciesAeadHkdfHybridDecrypt(
                 recipientPrivateKey,
                 modifiedSalt,
                 hmacAlgo,
                 EllipticCurves.PointFormatType.UNCOMPRESSED,
                 new RegistryEciesAeadHkdfDemHelper(keyTemplate));
-        try {
-          hybridDecrypt.decrypt(ciphertext, context);
-          fail("Invalid salt, should have thrown exception");
-        } catch (GeneralSecurityException expected) {
-          // Expected
+        assertThrows(
+            GeneralSecurityException.class, () -> hybridDecrypt.decrypt(ciphertext, modifiedSalt));
+        // The test takes too long in TSan, so we stop after the first case.
+        if (TestUtil.isTsan()) {
+          return;
         }
       }
     }
   }
 
   @Test
-  public void testModifyDecrypt() throws Exception {
-    testModifyDecrypt(CurveType.NIST_P256, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
-    testModifyDecrypt(CurveType.NIST_P384, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
-    testModifyDecrypt(CurveType.NIST_P521, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  public void testEncryptDecryptP256CtrHmac_mutatedSalt_throws() throws Exception {
+    testEncryptDecrypt_mutatedSalt_throws(
+        CurveType.NIST_P256, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
 
-    testModifyDecrypt(CurveType.NIST_P256, AeadKeyTemplates.AES128_GCM);
-    testModifyDecrypt(CurveType.NIST_P384, AeadKeyTemplates.AES128_GCM);
-    testModifyDecrypt(CurveType.NIST_P521, AeadKeyTemplates.AES128_GCM);
+  @Test
+  public void testEncryptDecryptP384CtrHmac_mutatedSalt_throws() throws Exception {
+    testEncryptDecrypt_mutatedSalt_throws(
+        CurveType.NIST_P384, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP521CtrHmac_mutatedSalt_throws() throws Exception {
+    testEncryptDecrypt_mutatedSalt_throws(
+        CurveType.NIST_P521, AeadKeyTemplates.AES128_CTR_HMAC_SHA256);
+  }
+
+  @Test
+  public void testEncryptDecryptP256Gcm_mutatedSalt_throws() throws Exception {
+    testEncryptDecrypt_mutatedSalt_throws(CurveType.NIST_P256, AeadKeyTemplates.AES128_GCM);
+  }
+
+  @Test
+  public void testEncryptDecryptP384Gcm_mutatedSalt_throws() throws Exception {
+    testEncryptDecrypt_mutatedSalt_throws(CurveType.NIST_P384, AeadKeyTemplates.AES128_GCM);
+  }
+
+  @Test
+  public void testEncryptDecryptP512Gcm_mutatedSalt_throws() throws Exception {
+    testEncryptDecrypt_mutatedSalt_throws(CurveType.NIST_P521, AeadKeyTemplates.AES128_GCM);
   }
 }
diff --git a/java_src/src/test/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridDecryptTest.java b/java_src/src/test/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridDecryptTest.java
new file mode 100644
index 0000000..3c5db74
--- /dev/null
+++ b/java_src/src/test/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridDecryptTest.java
@@ -0,0 +1,364 @@
+// 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.hybrid.subtle;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.crypto.tink.HybridDecrypt;
+import com.google.crypto.tink.HybridEncrypt;
+import com.google.crypto.tink.aead.subtle.AesGcmFactory;
+import com.google.crypto.tink.subtle.Hex;
+import com.google.crypto.tink.subtle.Random;
+import com.google.crypto.tink.testing.TestUtil;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for RsaKemHybridDecrypt */
+@RunWith(JUnit4.class)
+public final class RsaKemHybridDecryptTest {
+  @Test
+  public void decrypt_modifiedCiphertext() throws GeneralSecurityException {
+    if (TestUtil.isTsan()) {
+      // RsaKem.generateRsaKeyPair is too slow in Tsan.
+      return;
+    }
+    KeyPair keyPair = RsaKem.generateRsaKeyPair(2048);
+    String hmacAlgo = "HMACSHA256";
+    byte[] salt = Random.randBytes(20);
+
+    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+    HybridEncrypt hybridEncrypt =
+        new RsaKemHybridEncrypt(rsaPublicKey, hmacAlgo, salt, new AesGcmFactory(16));
+
+    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+    HybridDecrypt hybridDecrypt =
+        new RsaKemHybridDecrypt(rsaPrivateKey, hmacAlgo, salt, new AesGcmFactory(16));
+
+    byte[] plaintext = Random.randBytes(20);
+    byte[] context = Random.randBytes(20);
+    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
+
+    // Modifies ciphertext and makes sure that the decryption failed. This test implicitly checks
+    // the modification of public key and the raw ciphertext.
+    for (TestUtil.BytesMutation mutation : TestUtil.generateMutations(ciphertext)) {
+      assertThrows(
+          GeneralSecurityException.class, () -> hybridDecrypt.decrypt(mutation.value, context));
+    }
+  }
+
+  @Test
+  public void decrypt_modifiedContext() throws GeneralSecurityException {
+    if (TestUtil.isTsan()) {
+      // RsaKem.generateRsaKeyPair is too slow in Tsan.
+      return;
+    }
+    KeyPair keyPair = RsaKem.generateRsaKeyPair(2048);
+    String hmacAlgo = "HMACSHA256";
+    byte[] salt = Random.randBytes(20);
+
+    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+    HybridEncrypt hybridEncrypt =
+        new RsaKemHybridEncrypt(rsaPublicKey, hmacAlgo, salt, new AesGcmFactory(16));
+
+    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+    HybridDecrypt hybridDecrypt =
+        new RsaKemHybridDecrypt(rsaPrivateKey, hmacAlgo, salt, new AesGcmFactory(16));
+
+    byte[] plaintext = Random.randBytes(20);
+    byte[] context = Random.randBytes(20);
+    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
+
+    // Modifies context and makes sure that the decryption failed.
+    for (TestUtil.BytesMutation mutation : TestUtil.generateMutations(context)) {
+      assertThrows(
+          GeneralSecurityException.class, () -> hybridDecrypt.decrypt(ciphertext, mutation.value));
+    }
+  }
+
+  @Test
+  public void decrypt_modifiedSalt() throws GeneralSecurityException {
+    if (TestUtil.isTsan()) {
+      // RsaKem.generateRsaKeyPair is too slow in Tsan.
+      return;
+    }
+    KeyPair keyPair = RsaKem.generateRsaKeyPair(2048);
+    String hmacAlgo = "HMACSHA256";
+    byte[] salt = Random.randBytes(20);
+
+    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+    HybridEncrypt hybridEncrypt =
+        new RsaKemHybridEncrypt(rsaPublicKey, hmacAlgo, salt, new AesGcmFactory(16));
+
+    byte[] plaintext = Random.randBytes(20);
+    byte[] context = Random.randBytes(20);
+    byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
+
+    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+    // We exclude tests that modify the length of the salt, since the salt has fixed length and
+    // modifying the length may not be detected.
+    for (int bytes = 0; bytes < salt.length; bytes++) {
+      for (int bit = 0; bit < 8; bit++) {
+        byte[] modifiedSalt = Arrays.copyOf(salt, salt.length);
+        modifiedSalt[bytes] ^= (byte) (1 << bit);
+        HybridDecrypt hybridDecrypt =
+            new RsaKemHybridDecrypt(rsaPrivateKey, hmacAlgo, modifiedSalt, new AesGcmFactory(16));
+        assertThrows(
+            GeneralSecurityException.class, () -> hybridDecrypt.decrypt(ciphertext, context));
+      }
+    }
+  }
+
+  @Test
+  public void constructor_shortKey() throws GeneralSecurityException {
+    if (TestUtil.isTsan()) {
+      // RsaKem.generateRsaKeyPair is too slow in Tsan.
+      return;
+    }
+    KeyPair keyPair = RsaKem.generateRsaKeyPair(1024);
+    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+
+    assertThrows(
+        GeneralSecurityException.class,
+        () ->
+            new RsaKemHybridDecrypt(
+                rsaPrivateKey, "HMACSHA256", new byte[0], new AesGcmFactory(16)));
+  }
+
+  private static class RsaKemHybridTestVector {
+    public RSAPrivateKey privateKey;
+    public String hmacAlgo;
+    public byte[] salt;
+    public byte[] contextInfo;
+    int aesGcmKeySizeInBytes;
+    String plaintext;
+    public byte[] ciphertext;
+
+    public RsaKemHybridTestVector(
+        String privateKey,
+        String hmacAlgo,
+        String salt,
+        String contextInfo,
+        int aesGcmKeySizeInBytes,
+        String plaintext,
+        String ciphertext) {
+      try {
+        KeyFactory kf = KeyFactory.getInstance("RSA");
+        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Hex.decode(privateKey));
+        this.privateKey = (RSAPrivateKey) kf.generatePrivate(privateKeySpec);
+        this.hmacAlgo = hmacAlgo;
+        this.salt = Hex.decode(salt);
+        this.contextInfo = Hex.decode(contextInfo);
+        this.aesGcmKeySizeInBytes = aesGcmKeySizeInBytes;
+        this.plaintext = plaintext;
+        this.ciphertext = Hex.decode(ciphertext);
+      } catch (GeneralSecurityException ex) {
+        throw new IllegalArgumentException(ex);
+      }
+    }
+  }
+
+  // I can't find any public test vector.
+  private static final RsaKemHybridTestVector[] rsaKemHybridTestVectors = {
+    // These are generated by this very implementation, as a mean to verify implementations in other
+    // languages.
+    new RsaKemHybridTestVector(
+        "308204bc020100300d06092a864886f70d0101010500048204a6308204a20201000282010100a071550bcf139d"
+            + "2629d6e6612697f23a0a1abbae78658801f146d846c59141c280b8e361251701d2761ef97d5c38756bea"
+            + "03dafb12da3e8ba527400b5c7dd823e53e3d6b21ed015e818dfe590033ba24e3d483bcd2f3b7707900b3"
+            + "03a7f076236943ddb553be657ee68f19fa9a1ea7ce81a82b87d11ac69e1b01a8ad7b1107bc2f39b8a13c"
+            + "2f1fc69f657c2e318bec3cb04c8e26813cef9a3a50189e690031399eab70637b7cd6a7f850a39303053f"
+            + "8d16655437080b115e2ce5c6d60568eb6963eb4a8e0887384d1ee889b888effc17a275179c047d533ea6"
+            + "70a7cb3286a9a56dbcb00b26cf410732d656db9e5d94511c35ee38f156a0928b36f3be9f47a371020301"
+            + "00010282010079ff5344c473cc95dd509c23193e86a05d5890878ce2df3562ea94bfd3b0ad0921e9f473"
+            + "c4c926c88aaae8d8cacbdf756c1efc5ed7a9fdcf1f88a5e93dae2f30b43acc66ef0811777419ad62ad85"
+            + "a7b02f5143cecbb528322cc03c5eb64f60f5723079a04c8a4510e66dbfba4f80a9e69bae6d533bcc1964"
+            + "d5724079914f94c9edc29cebcf0bf26c6101ce232c705af830e0a4e2850ccff0fbb632fccf8b02a79c2c"
+            + "6572c88d6990a65227c7a37895f0a0c55907659179c2614db5123bacdb6203775b4ded2661046dbf95e1"
+            + "718e582e8b476ce334256e40c236fe2ce625350cfaa787f8fbf68bc78a6d650cd58319d21afd001ed935"
+            + "28a95c3c329413e5740102818100d32993fc23e8ac07120ad347c22df45e7cc4373424e9ce131d6452fa"
+            + "5d27d5c2f998f713d5d3f5fca03cff5a29876e4884c5403b53f4cdcc08678aadcccfc4805e3255eef4f0"
+            + "d95713ccde584b5a021c3dbb57fe51638bd0e9ec2f22cf69d5cea2e98439203b19f35f14b14e8b7b54c8"
+            + "8dccfc56fa887b827f1b0233933ecbc102818100c282b8047ca42034c3b59f682291a7fe1d366495c4d2"
+            + "b65b81632fd3687c040e81568b0c7e8225e1c5d7e7f63193c1929e826978d37f078b3e36e361d73bb4f0"
+            + "1b76065294f713bbca0a6fa955bf3aae110bebefa9ad0749a68d608cb4dd6341db78c79780e6e9e0c736"
+            + "3a15ef5ef614ce8bfc1f2e91ae5c602fa7dff8d983b10281807c20fbe20345da2249e3dd31589f65004a"
+            + "3d99e9e82d1cbbb5d26cc8ca0c09a794cbebaa584f4251dfec1b7b67e039df1d554a9dd58b99926ffa55"
+            + "d63708878da251da9c1e969a5c8ac22a8e22b5657a2e7bbb8d3f50d236dbdbc015a971a082d8d786a782"
+            + "1fdbf0699b236375b0e934ffce5923d42078e24c4f972ab44afcc102818057636b5178be344a0073a4fe"
+            + "a02958946e837585643c56f99d93f674c0c896f9fd59e876e08f907d72e9a1a996748fcf53afbbbb312a"
+            + "2d357dea23165e20d115df0093ae9e86b332f77ee0d3ef564f02cd5dd1ce8728d9d15926c36df4045307"
+            + "cf96dff54d50715b2fa5494b7993ec7344a8d7c91a9f10fb27abc17c7acd1ec102818012e5237bde4670"
+            + "639136fcf2a8308de2cd952d3256071af387754df91c811c7a63ce8a389689f3e9949580f5f659a60698"
+            + "17b6f5849128c56f7bb391b197cb333a926ce2430989c89a9e5de684015d3f4bb32eeebfc5e74101c33d"
+            + "5fd25917389a7f0577c3ac378e902ea391fca6f523c5b54518ea53e322cf75920560280b63",
+        "HMACSHA256",
+        "a19fd193ad1c247422d3",
+        "416efa391903a2e93dcfeecf7d5353ffc97f9b7d",
+        16,
+        "8012627431bbe7a54375d1accb467c1f7c82d1d758f50a2feda4d42cfabd",
+        "47c8f7b8d060fe7a6f423c0ee287f5a84a28615403c84d5c113aea0bc9b102e8a77ca7120aba15cea3d7bf13cd"
+            + "47f802c8490e02f3209c37ed278b23d2e4230552c0e32f35ca6854c5b3c26082ce2439f311e684f3850b"
+            + "91e780b7839cef502e69f02688aaa768517cfb88288e710b6093e87ebe250bef08e861d6cc2925767d5e"
+            + "1d9185c5fcc3208bc12281e72155bb7453133b1c78da8962a261e9be0b38a97d6fdc4319d66cf241268c"
+            + "b2aaf9fe5905df60db9e3d690b87a2924fe83904b2a21a420d5beecaa61abc5d9438e370886bb8965214"
+            + "bd3624e1c30e1635c36043730d17e8bd63849cb2fefe036a911c8240fc6c2bc2d71768938a48b87e321a"
+            + "ae063b578bfd88000aa61a85794f4b29b64b8e41216e255fc6221296617a1eb3a66666d610c71fbddfd7"
+            + "2911390b05f50d79d0b43ca30e26be996b"),
+    new RsaKemHybridTestVector(
+        "308204be020100300d06092a864886f70d0101010500048204a8308204a402010002820101009540ff714a59a8"
+            + "d4f567da845a5034263946e9fded30c10adff76ad99b10256778baf65b4a05e3e21314e089efb316d559"
+            + "6382290847ee422204f092e0f79b76a1c22d0921daa754489ccbd6a1c59d5e2e98b02b25e42e9272a77d"
+            + "e716eb4068fc22a86756a67a29f96c6e8f60e972aefd68762d5c91583483bd4875ef85fa6039aae30f1b"
+            + "6b27f33a970b04bb399f0aa1c49fcd1880b98402582275e6a75a4f1045d312187019a485ecd3b4eeb0b6"
+            + "076ab532f6c14664841e9d01b6750985f6ba2438ffa4641403e9991c4e6afac0f8ea71bd9645eff3a22c"
+            + "73f77c2d189faa029f06ed5181589e4daacb566229e4dc55fc7c05f33ad93f39de55a85f50d6d9020301"
+            + "00010282010039cb265e96fcaadc737e58660196a6eada28f47867fd05f311107c2670ddcaae0b58d206"
+            + "3d5e948439014f84f9f52df5451cbc0ce970f8f850b5faf5d4f8ec10fec7f2aa639a884aa1a75d62e9d7"
+            + "5c7d58abb523b013705932de5a693e3daffe370bb08bfb48916b6972ac4906acbec4b5c95a616c43b794"
+            + "f6223849ba8af58ced7eae465f20b517ba91b1bbff2f5ae85810885394a9a1435f363c9b29fece9edec8"
+            + "8764af6a05256773a87bc92bd5a017481bea52398f0b0371db4d659e97527ccf6983d81d746da3964fd3"
+            + "5fed63d4ab9c006055c579706940d9f015c6cd7a0c93c827f026c0ad4a59095417da61d80f07b6bacf91"
+            + "a11b6ac51221a94cef2502818100e873d0c21ff9c5ca8e8bb8b848f6a963c1111b639d7c0bf710344c31"
+            + "318b2282c559f0c874c9462415ca4b15ad89d4f90c69b4cfd3ac4a9e2064f4dde2e9cc44222c944221ea"
+            + "7050c7d0f9f01e716a2e2d8755064dd7b78e9b557a9124671f0087c25bf7ce0490ffc6b4da2460bd9676"
+            + "ab169b97ec31967abd2b243378d678bb02818100a45f98c819c4298d2928ed23f97efdd8a439c5d741c4"
+            + "7827ca39e93d58eadca31c213ca715cf1c8b321d3010f55afc2c4f620c1ab341412a2c0caa2f1af9e57d"
+            + "0168267a5401e411cbe24ee114a6eb4b815708ea74b3b78b07d49f639641f55997efdb8e1eb1475f2513"
+            + "23c64e3a33ec0f3bf8120b111f5e639384ac99e7af7b02818100e6e191ea1ee471a69d2afe505c78530a"
+            + "f7cacc0f876e9c5bcb46869f1dfc7a4cb5447e3a3c75662b9551167ef39d41621508314573935f91ebcf"
+            + "1ac0011003897100224a0571dc19003efae19afb3f619a6b1ef26202ef18c00488f6fcd7481db8ba3daa"
+            + "c680169d567a6f694e85409ba19794f7b2ec15f0d74fb06747908edd0281806f751f2d38438a855c8e92"
+            + "d69cfc5e76c34d257903f08c2536fc33cad47b552709110486abc427afbf48896a4664eeafc11853eada"
+            + "f7f98ef6159464a29f26dcafd2869cd64ffded8f59a270ff46fc2fd3c1479b6b8cdd7d59cef4515bf6d7"
+            + "be6bc74a12417fa64cbee00e970e3e6b2cbb5bc7a7bc775cd4ed227f896646f2f702818100b1c5021466"
+            + "a2fa1a2c3fc686a2595e2f04ac7124c4a9134146f30120029046bd20839074fab913e09fccb8debdba4b"
+            + "68d788a8e2aa10041603b875237569c759c628bb722c3c73d1fd27373344ecae1d7d4ed186fc7adbc84d"
+            + "f87275564fed44f160eceef0b2180152ddd581b483d8d87f7155fec2ca563594d413fb7c4fac99",
+        "HMACSHA256",
+        "",
+        "dbe8324084cef0bd79b1e371573f81a455eba51d",
+        16,
+        "c1ce9e69a387d0f6bc50e0644528726ad6415708582f42977afd352d5493",
+        "39f8503afcb5ab22ee8787cfbc8fbe0462b2a9449389e1c7a042f80505e5260388ee0cd5c33c6b586475d1b078"
+            + "bd6e50e12c85c5e88fc02fe1604dc1182f9afb7066daf04e34cc02747163acada9ec80da24044b3be84c"
+            + "624acb0a8fc8f1cea11d307eb5ec51aa46c1e79ec6c5afad8bfafa3d26495a7acb2652a914531541e270"
+            + "d82e93c91806fb353b20dc62236a3f949c01849e286a7d0f892247dfcf301d5e107f33d1c9627565aa8e"
+            + "28d9b9cc49fe430d69294813bd87d940b9f8fbf98cb9fe5de09e0c763ea2eb9cae4c678b71ab21d861f9"
+            + "821a7ce9d1453bebc702945862edcb25d9db71ea3abf711a069443602005d8abdf9289b80d1512e356f1"
+            + "f6f5fd5673833a615cacec69814ab293530733fa1aadcf76fe8c10595858f5ef744d509fd972b914bc0e"
+            + "537f44b3e463d3063299637d5604c56b30"),
+    // These are generated by an independent implementation in cr/309235397.
+    new RsaKemHybridTestVector(
+        "30820943020100300d06092a864886f70d01010105000482092d3082092902010002820201009765065f3e44a8"
+            + "1f63a771e53dd3ce7fb50830a82e06853c258a8f3761774083b40c967203439becdd10bc9c40c3a7c408"
+            + "113be9bc845b5a8c943b48b9076245990a480e8a4a6cc2ada41a027194b2f80db2056bc78fbf0cac1560"
+            + "93d3442fd981863d50e668f11c0f33f34d5c2b20753ff800952e5c358b1ab46885021c38ccbed1f31a2c"
+            + "53ec641e2b36ff1c049885fdd0c5d02cb1ba2ddd46751f868b0f4a0f06be76c2d81a7579f60d73486e35"
+            + "c9618ddd20243b1cdc367e3c178d001a139b0c7131b789beb214fff2628a66ce5a3a9cf046f0f41bd259"
+            + "34da45b0f0761dbbf5a5c43aa017f08515e4d9e409d9609778fc9f2ae855f01b99c8c867f9293cc82d6c"
+            + "d24b11466185f5d7ff3d25d9ce41835fa8c8381e2c86a2ad7efb52b97aee0401561b3980eecd5ae37b99"
+            + "768dd40cb7fe327992e84b8b730107bb0f6d983c053701b0abfaa51496ac9069032229f9b0c5e76eb829"
+            + "a5b1ed13f7dbc71abb0ab58b442d499fe15a91a48fea210930fde25193cc5fc6d3221861d70553afbc6d"
+            + "b801b35e1acd11437358a76b20f16999ad3db75a70c92c539932ea91c66713d1242a4ab770f53a05de10"
+            + "a76052a5dd7bacb4ebb8abf764ee5cbf355c5634543c66b96eedeba52dc9b494a4de2b061b6ad5ada7ab"
+            + "60599a2010f910bd42c84560c69f53300d55e417206044a779c82d4dabcd1a48b3abbd46ce7645a29771"
+            + "570203010001028202000a8fc5ae074e0adc85bb3cbadd61555a9b6a82da78bb83b7fa767c1f44f168d2"
+            + "c750c97e12b438c0817b956ce127c5206bdfa7ea5a78715713cf938333454c99c65f6d3f766e52c01215"
+            + "0cab76f904b3416155f77ae8904f7601d7c6f84a1d7c7308c6664a6cafc5615a22731726fb10b2f1be38"
+            + "aa0f4cbcf393fa66ad0b02fb65a487ed1f828b4f40bb8b0ea909b90a3ff6bc96154820d0ea4aec08d2fe"
+            + "0887061d56543aab90ccf9412d1709ca867e120c2f8cfa7a153d579fded955c0d810de1434215fc88041"
+            + "342dd6df0100c30c90623a70f8864f1a91969a539a506c2d0edbc840b4464ee53ca3965f8d0512e12630"
+            + "0e8f9c12b14c1ae0d39d0783de587f8bc6e4ce6ce7f62a579f7de54efada511d4c37362ba28d9a4a95da"
+            + "25891e5f499a3b89c007e18e73e08dc4db23380c564994888a437afb5ece1f1f153053b07c6cb233032d"
+            + "c0033f4305eca759751332b4a499bce738912884c3bd926b1cdc4a5c5bd70b3a1fb4d235050b056d36ac"
+            + "9354834da61cd0b9005c3b216fd3adfd1139593c4bfb91d5cd94c866b4d3fab126f13f06e25b2894cecd"
+            + "192db9220da70ffb47e3745e53e2356e18e10a4faeb788d6574bc202ada02d7392accca1c00c94b36adf"
+            + "53b2dba6e610f367f6c7782d61e2a96f835ef7e9fce6012fb8c73863c88139736216535a43ecec6580b5"
+            + "dda2d0cf69593128e32a62cdcd65e25623810282010100dbd85e276c5cc0f46026b3199cd27581846f69"
+            + "29e4ca796633343e8f69c176498b4fe3993c6f48ff9c4310ef1e24a7a1a7481580d39a0212c4cd066ad5"
+            + "d6e609d57eaffcda723154c6cc2254b7ba135d019f7cc6b044083c55e6e26f42655f3d65803d928d3b56"
+            + "7ec8526f69ac5e526f42b8d4f2c4bdf6176c89a835c55820a06980b1681b2a645aebe1f322106ca265b2"
+            + "4148f15805c2b13432125eb9319e7e36c724262d7b1e2d34fca67759acce9c07a5e6d1c96fb7759e22a8"
+            + "89bf61afc34da81f8348b1b45c7acb8c23557f00fc1ade95989c72fe19fb759244a1c819d49b06ee6855"
+            + "ae44f5bd6727aadc79cb9020e1103eea378fe4b676bc7d47b487b10282010100b04ad789a5f1c180ba6d"
+            + "2a6946e971aa5c57f9d3a3e2843abe7abe67a6e2cb1f0572345c23753e85d83b57825a2af9426435be05"
+            + "41db0a91d1176cc56abdbe62458911732d8838cbc0c921355998189e65269a128cad9be72bc3361b2e0f"
+            + "933591e9079fc27b6bb63acb74c150c8fabe7bb78fc50291152abd1a831c7e2f31e52ffab417e7578eef"
+            + "437d5a207f8263c8030f586f07e72a517f9aa44ecbd3c9a44b2b8890b3f79e2192600dbc471c7d09eee1"
+            + "d79d553aed21163b6ce0f50eaf36da964f687a5349302d1954284549c1360ac160f3b045a4763e23e861"
+            + "62fc4bb983658b7ae494111f3780506e0e736cc929156c6acb715703e7750e037f9dd3870282010100ba"
+            + "4beffd9866415cd4ddf6878dcd0aa66683c2aa2da72698e46b31587655ead707a6fb47af5ede8d3cedd8"
+            + "3bc95f666e26437f755bdaf646d15eac417c544f3ba61f6522f03a347392c30994a0dc9dec02a414288a"
+            + "d61be48526d25b55f8716ca5c6b666aa27ce74416d19dc82a4ab567d4403b075e843d235b7b1435fa7fe"
+            + "7df0e98d6c9b18a1522af19e070fc3ff1a0ea4241be06b814088eaa5867f88fcb617d5495cd0cdb414bb"
+            + "021e4ea53f3b161da508a45dfebd887e2900893a149dccf2d1b5629b077bbfa28f3a81f6c1592449e0b5"
+            + "044e0f6424c0623140d797a9cbf0533f544ac712c8eb67aec5ab6fca80a85c10584042353dab219338d6"
+            + "bab50102820101009a8a9155f665eef694f6dbc5fc46eac0a840eb1dafbe03a2a7965c51eb07477ec33c"
+            + "71501039587ce6a866b73baa0e663808b0b2551fdaad2739bcbd772c2cb863329c5c769ec30342d64e49"
+            + "416846b49c0171f12ee78612e9d730183591abbfbb5027c1d23075a502f7963b5d41422637b81bcd5dc9"
+            + "a75f96f4a5d91578f3e970dcfa8135e918c1004de3f337342b9a8bac291ef4339e72614544225b2626ce"
+            + "e2a2a00e11e5d0f6a7259304e8e5bd6b36c13e4d8b08a4156c32dde87a8acbe86f48730628add82be66d"
+            + "1ccc4ca93239d8c5dae2e534b7ce7bfce85a6ef6b2bf46c37eb955a5c338b563c39e2706e267999f5132"
+            + "7173c30f06192416c709a9030282010056c8258f69c9e0fd3d58536d95fbcb074dbf2b90c8fc7e66c338"
+            + "54c7476d60665204cc67fa1a37cc30427a3d70c759058bcd9cf8528009d4bda6ce2f12320013a3df8238"
+            + "59f70afe736ac0771bfe9afdbf8f531c9904fe4ea072f6726c04f51ab947539106c058bbe7afc9bb6530"
+            + "37cb2591503216958e32a143b2a4bf578b8fc495babe031f4ca4ad0a424997828a4b19883ce165335fb7"
+            + "675db88bc79315cecab0a73c778a5f8bc3ef068eef0e5902bc5d3e5aa0947f5acb5e0376699a5ff50ee8"
+            + "9cca30ac7e855cf17cbccdca0e54ca928c5f35fe12d43aaa21b34009ff90961c07e929eec10d8e22bed4"
+            + "7f44eb45f85f7dd52b811ae4aa23f1beb96714ee",
+        "HMACSHA256",
+        "2a9226902d4a4c91a3a0b566a7de001e",
+        "73696d706c6520617320646f207265206d69",
+        32,
+        "6162632c2065617379206173203132332e",
+        "3e6060a0c1f06e7475ae58d78510cce4ca84ba64705b6195991106818745db0dde5826e789b11ac9e230ff146f"
+            + "7b8e72ff48a6e92adb23036eee424b2af26f6059d315a963bac676ee7827a9c7ea75b42aef28d06d50a8"
+            + "58cfb0f54d663e2c87134201f5fbf67af80a0b185faf721a894e8d43f401fe882a688a0a91cbdd13409f"
+            + "0deeeb86c170a46134372d206581b0f46faaf1ce8b0c30cef83b7d8296c9af37380c95bffa0a832e97dd"
+            + "9f6d4545539893b342665c795fe75744fb0661a73a853ced4ef472857292b6e27aa5729def89b4d53fb8"
+            + "3f0a2825186b80ceecfdebadb2190e6dde117e41d1f365637edf0ee5872e834908a9dbe1cf06131bd994"
+            + "b26c5306a426dd9d621ab972bf5584b3feadf8ab1b0b95ea32ecb3bcb1a51738cf4aae5abe92f34e874b"
+            + "93f07494fa16ed956d4023f68d8b34418136270417134a3ec5372a6ac7df61b2caeecb5101b390b43964"
+            + "a78006127a4c26b05679ba42fbf206862ad85355c12438c5c7143de914974cda72f0e8b752b9e7d4b8ce"
+            + "d1034038291fc02f30bfa630fb10f4c35d09fc2231adf3594be02f248b2e28dcc7a5382c1129dd86ea58"
+            + "1eb2c5abbe06724e762cae49e069c730bb9dda76df7beb99483e55383ae95fcc90772a7a23f5ee556580"
+            + "35a7d1fd812614e6e688039150e8af6afff12e9a8503d530dd1cec8d25b81a59b3a0ba0d7d2e48179bb3"
+            + "756256829f93bf1804641f5d620810037843f17e4009c30957ef91406b1daffd68275e6856903896cfc7"
+            + "749db8814299904a")
+  };
+
+  @Test
+  public void decryptWithTestVectors() throws GeneralSecurityException {
+    for (RsaKemHybridTestVector vector : rsaKemHybridTestVectors) {
+      String hmacAlgo = vector.hmacAlgo;
+      byte[] salt = vector.salt;
+      RSAPrivateKey rsaPrivateKey = vector.privateKey;
+      HybridDecrypt hybridDecrypt =
+          new RsaKemHybridDecrypt(
+              rsaPrivateKey, hmacAlgo, salt, new AesGcmFactory(vector.aesGcmKeySizeInBytes));
+      byte[] plaintext = hybridDecrypt.decrypt(vector.ciphertext, vector.contextInfo);
+      assertThat(Hex.encode(plaintext)).isEqualTo(vector.plaintext);
+    }
+  }
+}
diff --git a/java_src/src/test/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridEncryptTest.java b/java_src/src/test/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridEncryptTest.java
new file mode 100644
index 0000000..ae7aaeb
--- /dev/null
+++ b/java_src/src/test/java/com/google/crypto/tink/hybrid/subtle/RsaKemHybridEncryptTest.java
@@ -0,0 +1,92 @@
+// 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.hybrid.subtle;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThrows;
+
+import com.google.crypto.tink.HybridDecrypt;
+import com.google.crypto.tink.HybridEncrypt;
+import com.google.crypto.tink.aead.subtle.AesGcmFactory;
+import com.google.crypto.tink.subtle.Random;
+import com.google.crypto.tink.testing.TestUtil;
+import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.Set;
+import java.util.TreeSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests for RsaKemHybridEncrypt */
+@RunWith(JUnit4.class)
+public final class RsaKemHybridEncryptTest {
+  private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  @Test
+  public void encrypt_decrypt_success() throws GeneralSecurityException {
+    if (TestUtil.isTsan()) {
+      // RsaKem.generateRsaKeyPair is too slow in Tsan.
+      return;
+    }
+    KeyPair keyPair = RsaKem.generateRsaKeyPair(2048);
+    String hmacAlgo = "HMACSHA256";
+    byte[] salt = Random.randBytes(20);
+
+    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+    HybridEncrypt hybridEncrypt =
+        new RsaKemHybridEncrypt(rsaPublicKey, hmacAlgo, salt, new AesGcmFactory(16));
+
+    RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
+    HybridDecrypt hybridDecrypt =
+        new RsaKemHybridDecrypt(rsaPrivateKey, hmacAlgo, salt, new AesGcmFactory(16));
+
+    byte[] plaintext = Random.randBytes(20);
+    byte[] context = Random.randBytes(20);
+
+    // Makes sure that the encryption is randomized.
+    Set<String> ciphertexts = new TreeSet<>();
+    for (int j = 0; j < 8; j++) {
+      byte[] ciphertext = hybridEncrypt.encrypt(plaintext, context);
+      if (ciphertexts.contains(new String(ciphertext, UTF_8))) {
+        throw new GeneralSecurityException("Encryption is not randomized");
+      }
+      ciphertexts.add(new String(ciphertext, UTF_8));
+      byte[] decrypted = hybridDecrypt.decrypt(ciphertext, context);
+      assertArrayEquals(plaintext, decrypted);
+    }
+    assertThat(ciphertexts).hasSize(8);
+  }
+
+  @Test
+  public void constructor_shortKey() throws GeneralSecurityException {
+    if (TestUtil.isTsan()) {
+      // RsaKem.generateRsaKeyPair is too slow in Tsan.
+      return;
+    }
+    KeyPair keyPair = RsaKem.generateRsaKeyPair(1024);
+    RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
+
+    assertThrows(
+        GeneralSecurityException.class,
+        () ->
+            new RsaKemHybridEncrypt(
+                rsaPublicKey, "HMACSHA256", new byte[0], new AesGcmFactory(16)));
+  }
+}
diff --git a/java_src/src/test/java/com/google/crypto/tink/hybrid/subtle/RsaKemTest.java b/java_src/src/test/java/com/google/crypto/tink/hybrid/subtle/RsaKemTest.java
new file mode 100644
index 0000000..285ad1f
--- /dev/null
+++ b/java_src/src/test/java/com/google/crypto/tink/hybrid/subtle/RsaKemTest.java
@@ -0,0 +1,87 @@
+// 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.hybrid.subtle;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.util.Random;
+import java.util.Set;
+import java.util.TreeSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for RsaKem * */
+@RunWith(JUnit4.class)
+public final class RsaKemTest {
+  private static final Charset UTF_8 = Charset.forName("UTF-8");
+
+  @Test
+  public void bigIntToByteArray_approximate() throws Exception {
+    int expectedSize = 2048;
+    BigInteger bigInt = new BigInteger(2048, new Random());
+    byte[] bigIntBytes = RsaKem.bigIntToByteArray(bigInt, expectedSize);
+
+    assertThat(bigIntBytes).hasLength(expectedSize);
+    assertThat(new BigInteger(1, bigIntBytes)).isEqualTo(bigInt);
+  }
+
+  @Test
+  public void bigIntToByteArray_withALeadingZero() throws Exception {
+    int expectedSize = 2048;
+    BigInteger bigIntWithALeadingZero;
+    while (true) {
+      bigIntWithALeadingZero = new BigInteger(2048, new Random());
+      byte[] r = bigIntWithALeadingZero.toByteArray();
+      if (r[0] == 0) {
+        break;
+      }
+    }
+
+    byte[] modBytes = RsaKem.bigIntToByteArray(bigIntWithALeadingZero, expectedSize);
+    assertThat(modBytes).hasLength(expectedSize);
+    assertThat(new BigInteger(1, modBytes)).isEqualTo(bigIntWithALeadingZero);
+  }
+
+  @Test
+  public void bigIntToByteArray_small() throws Exception {
+    int expectedSize = 2048;
+    BigInteger smallInt = BigInteger.valueOf(42L);
+    byte[] smallIntBytes = RsaKem.bigIntToByteArray(smallInt, expectedSize);
+
+    assertThat(smallIntBytes).hasLength(expectedSize);
+    assertThat(new BigInteger(1, smallIntBytes)).isEqualTo(smallInt);
+  }
+
+  @Test
+  public void generateSecret() throws Exception {
+    BigInteger max = new BigInteger(2048, new Random());
+    int maxSizeInBytes = RsaKem.bigIntSizeInBytes(max);
+
+    Set<String> secrets = new TreeSet<>();
+    for (int i = 0; i < 100; i++) {
+      byte[] secret = RsaKem.generateSecret(max);
+      BigInteger secretBigInt = new BigInteger(1, secret);
+      secrets.add(new String(secret, UTF_8));
+
+      assertThat(secret).hasLength(maxSizeInBytes);
+      assertThat(secretBigInt.signum()).isEqualTo(1);
+      assertThat(secretBigInt.compareTo(max)).isLessThan(0);
+    }
+    assertThat(secrets).hasSize(100);
+  }
+}
diff --git a/java_src/src/test/java/com/google/crypto/tink/mac/HmacKeyManagerTest.java b/java_src/src/test/java/com/google/crypto/tink/mac/HmacKeyManagerTest.java
index a5ea1b9..e5b06a2 100644
--- a/java_src/src/test/java/com/google/crypto/tink/mac/HmacKeyManagerTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/mac/HmacKeyManagerTest.java
@@ -27,7 +27,8 @@
 import com.google.crypto.tink.proto.HmacKey;
 import com.google.crypto.tink.proto.HmacKeyFormat;
 import com.google.crypto.tink.proto.HmacParams;
-import com.google.crypto.tink.subtle.MacJce;
+import com.google.crypto.tink.subtle.PrfHmacJce;
+import com.google.crypto.tink.subtle.PrfMac;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.testing.TestUtil;
 import com.google.protobuf.ByteString;
@@ -234,8 +235,10 @@
     HmacKey validKey = factory.createKey(makeHmacKeyFormat(16, 19, HashType.SHA1));
     Mac managerMac = manager.getPrimitive(validKey, Mac.class);
     Mac directMac =
-        new MacJce(
-            "HMACSHA1", new SecretKeySpec(validKey.getKeyValue().toByteArray(), "HMAC"), 19);
+        new PrfMac(
+            new PrfHmacJce(
+                "HMACSHA1", new SecretKeySpec(validKey.getKeyValue().toByteArray(), "HMAC")),
+            19);
     byte[] message = Random.randBytes(50);
     managerMac.verifyMac(directMac.computeMac(message), message);
   }
@@ -245,8 +248,10 @@
     HmacKey validKey = factory.createKey(makeHmacKeyFormat(16, 29, HashType.SHA256));
     Mac managerMac = manager.getPrimitive(validKey, Mac.class);
     Mac directMac =
-        new MacJce(
-            "HMACSHA256", new SecretKeySpec(validKey.getKeyValue().toByteArray(), "HMAC"), 29);
+        new PrfMac(
+            new PrfHmacJce(
+                "HMACSHA256", new SecretKeySpec(validKey.getKeyValue().toByteArray(), "HMAC")),
+            29);
     byte[] message = Random.randBytes(50);
     managerMac.verifyMac(directMac.computeMac(message), message);
   }
@@ -256,8 +261,10 @@
     HmacKey validKey = factory.createKey(makeHmacKeyFormat(16, 33, HashType.SHA512));
     Mac managerMac = manager.getPrimitive(validKey, Mac.class);
     Mac directMac =
-        new MacJce(
-            "HMACSHA512", new SecretKeySpec(validKey.getKeyValue().toByteArray(), "HMAC"), 33);
+        new PrfMac(
+            new PrfHmacJce(
+                "HMACSHA512", new SecretKeySpec(validKey.getKeyValue().toByteArray(), "HMAC")),
+            33);
     byte[] message = Random.randBytes(50);
     managerMac.verifyMac(directMac.computeMac(message), message);
   }
diff --git a/java_src/src/test/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManagerTest.java b/java_src/src/test/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManagerTest.java
index 9d1ab1d..cc036fb 100644
--- a/java_src/src/test/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManagerTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/signature/RsaSsaPssSignKeyManagerTest.java
@@ -168,6 +168,18 @@
     }
   }
 
+  @Test
+  public void validateKeyFormat_negativeSaltLength_throws() throws Exception {
+    RsaSsaPssKeyFormat format =
+        createKeyFormat(HashType.SHA512, HashType.SHA512, -5, 3072, RSAKeyGenParameterSpec.F4);
+    try {
+      factory.validateKeyFormat(format);
+      fail();
+    } catch (GeneralSecurityException e) {
+      // expected
+    }
+  }
+
   private static void checkConsistency(RsaSsaPssPrivateKey privateKey,
       RsaSsaPssKeyFormat keyFormat) {
     assertThat(privateKey.getPublicKey().getParams()).isEqualTo(keyFormat.getParams());
diff --git a/java_src/src/test/java/com/google/crypto/tink/subtle/AeadThreadSafetyTest.java b/java_src/src/test/java/com/google/crypto/tink/subtle/AeadThreadSafetyTest.java
index 481803f..abf4d61 100644
--- a/java_src/src/test/java/com/google/crypto/tink/subtle/AeadThreadSafetyTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/subtle/AeadThreadSafetyTest.java
@@ -150,7 +150,7 @@
     int macSize = 12;
     IndCpaCipher cipher = new AesCtrJceCipher(key, ivSize);
     SecretKeySpec keySpec = new SecretKeySpec(macKey, "HMAC");
-    Mac mac = new MacJce("HMACSHA256", keySpec, macSize);
+    Mac mac = new PrfMac(new PrfHmacJce("HMACSHA256", keySpec), macSize);
 
     // TODO(b/148134669): Remove the following line.
     // There is a potential (but unlikely) race in java.security.Provider. Since AesCtrHmac
diff --git a/java_src/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java b/java_src/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java
index 7491abc..24860ae 100644
--- a/java_src/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/subtle/EcdsaVerifyJceTest.java
@@ -52,6 +52,15 @@
         "../wycheproof/testvectors/ecdsa_secp384r1_sha512_test.json", EcdsaEncoding.DER);
     testWycheproofVectors(
         "../wycheproof/testvectors/ecdsa_secp521r1_sha512_test.json", EcdsaEncoding.DER);
+    testWycheproofVectors(
+        "../wycheproof/testvectors/ecdsa_secp256r1_sha256_p1363_test.json",
+        EcdsaEncoding.IEEE_P1363);
+    testWycheproofVectors(
+        "../wycheproof/testvectors/ecdsa_secp384r1_sha512_p1363_test.json",
+        EcdsaEncoding.IEEE_P1363);
+    testWycheproofVectors(
+        "../wycheproof/testvectors/ecdsa_secp521r1_sha512_p1363_test.json",
+        EcdsaEncoding.IEEE_P1363);
   }
 
   private static void testWycheproofVectors(String fileName, EcdsaEncoding encoding)
@@ -141,18 +150,42 @@
   }
 
   @Test
-  public void testBasic() throws Exception {
+  public void testAgainstJCEInstance256() throws Exception {
     testAgainstJceSignatureInstance(EllipticCurves.getNistP256Params(), HashType.SHA256);
+  }
+
+  @Test
+  public void testAgainstJCEInstance384() throws Exception {
     testAgainstJceSignatureInstance(EllipticCurves.getNistP384Params(), HashType.SHA512);
+  }
+
+  @Test
+  public void testAgainstJCEInstance512() throws Exception {
     testAgainstJceSignatureInstance(EllipticCurves.getNistP521Params(), HashType.SHA512);
+  }
+
+  @Test
+  public void testSignVerify256() throws Exception {
     testSignVerify(EllipticCurves.getNistP256Params(), HashType.SHA256);
+  }
+
+  @Test
+  public void testSignVerify384() throws Exception {
     testSignVerify(EllipticCurves.getNistP384Params(), HashType.SHA512);
+  }
+
+  @Test
+  public void testSignVerify512() throws Exception {
     testSignVerify(EllipticCurves.getNistP521Params(), HashType.SHA512);
   }
 
   private static void testAgainstJceSignatureInstance(ECParameterSpec ecParams, HashType hash)
       throws Exception {
-    for (int i = 0; i < 100; i++) {
+    int numSignatures = 100;
+    if (TestUtil.isTsan()) {
+      numSignatures = 5;
+    }
+    for (int i = 0; i < numSignatures; i++) {
       KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
       keyGen.initialize(ecParams);
       KeyPair keyPair = keyGen.generateKeyPair();
@@ -173,7 +206,11 @@
   }
 
   private static void testSignVerify(ECParameterSpec ecParams, HashType hash) throws Exception {
-    for (int i = 0; i < 100; i++) {
+    int numSignatures = 100;
+    if (TestUtil.isTsan()) {
+      numSignatures = 5;
+    }
+    for (int i = 0; i < numSignatures; i++) {
       KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
       keyGen.initialize(ecParams);
       KeyPair keyPair = keyGen.generateKeyPair();
diff --git a/java_src/src/test/java/com/google/crypto/tink/subtle/EllipticCurvesTest.java b/java_src/src/test/java/com/google/crypto/tink/subtle/EllipticCurvesTest.java
index 8e39bea..59bff92 100644
--- a/java_src/src/test/java/com/google/crypto/tink/subtle/EllipticCurvesTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/subtle/EllipticCurvesTest.java
@@ -549,6 +549,10 @@
 
   @Test
   public void testComputeSharedSecretWithWycheproofTestVectors() throws Exception {
+    if (TestUtil.isTsan()) {
+      return;
+    }
+
     // NOTE(bleichen): Instead of ecdh_test.json it might be easier to use the
     //   files ecdh_<curve>_ecpoint.json, which encode the public key point just as DER
     //   encoded bitsequence.
diff --git a/java_src/src/test/java/com/google/crypto/tink/subtle/HkdfTest.java b/java_src/src/test/java/com/google/crypto/tink/subtle/HkdfTest.java
index 856f3cc..b28465e 100644
--- a/java_src/src/test/java/com/google/crypto/tink/subtle/HkdfTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/subtle/HkdfTest.java
@@ -133,6 +133,9 @@
         + "673a081d70cce7acfc48",
         computeHkdfHex("HmacSha1", "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "", "", 42));
   }
+
+
+
   /**
    * Test version of Hkdf where all inputs and outputs are hexadecimal.
    */
diff --git a/java_src/src/test/java/com/google/crypto/tink/subtle/MacJceTest.java b/java_src/src/test/java/com/google/crypto/tink/subtle/PrfHmacJceTest.java
similarity index 66%
rename from java_src/src/test/java/com/google/crypto/tink/subtle/MacJceTest.java
rename to java_src/src/test/java/com/google/crypto/tink/subtle/PrfHmacJceTest.java
index 724e25e..894cd0c 100644
--- a/java_src/src/test/java/com/google/crypto/tink/subtle/MacJceTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/subtle/PrfHmacJceTest.java
@@ -17,9 +17,12 @@
 package com.google.crypto.tink.subtle;
 
 import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
 import com.google.crypto.tink.Mac;
+import com.google.crypto.tink.prf.Prf;
+import com.google.crypto.tink.util.TestUtil;
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.util.Arrays;
@@ -28,9 +31,12 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-/** Unit tests for MacJce. */
+/**
+ * Unit tests for PrfHmacJce. Note that this used to be a Mac primitive, so all these tests first
+ * convert the Prf to a Mac.
+ */
 @RunWith(JUnit4.class)
-public class MacJceTest {
+public class PrfHmacJceTest {
   private static class MacTestVector {
     String algName;
     public byte[] key;
@@ -75,7 +81,8 @@
   @Test
   public void testMacTestVectors() throws Exception {
     for (MacTestVector t : HMAC_TEST_VECTORS) {
-      Mac mac = new MacJce(t.algName, new SecretKeySpec(t.key, "HMAC"), t.tag.length);
+      Mac mac =
+          new PrfMac(new PrfHmacJce(t.algName, new SecretKeySpec(t.key, "HMAC")), t.tag.length);
       assertArrayEquals(t.tag, mac.computeMac(t.message));
       try {
         mac.verifyMac(t.tag, t.message);
@@ -86,9 +93,37 @@
   }
 
   @Test
+  public void testPrfUniformity() throws GeneralSecurityException {
+    for (MacTestVector t : HMAC_TEST_VECTORS) {
+      Prf prf = new PrfHmacJce(t.algName, new SecretKeySpec(t.key, "HMAC"));
+      // We need a string of bytes identical in size to the tag output size for the given algorithm
+      // so we can test cross correlation. We're not actually validating the output contents of the
+      // HMAC in this function. Therefore - just feed the test tag into the HMAC.
+      byte[] prBytes = prf.compute(t.tag, t.tag.length);
+      TestUtil.ztestUniformString(prBytes);
+      TestUtil.ztestAutocorrelationUniformString(prBytes);
+      TestUtil.ztestCrossCorrelationUniformStrings(prBytes, t.tag);
+    }
+  }
+
+  @Test
+  public void testPrfPrefixOfMac() throws Exception {
+    for (MacTestVector t : HMAC_TEST_VECTORS) {
+      Prf prf = new PrfHmacJce(t.algName, new SecretKeySpec(t.key, "HMAC"));
+      Mac mac = new PrfMac(prf, t.tag.length);
+      byte[] prBytes = prf.compute(t.message, t.tag.length - 1);
+      byte[] tag = mac.computeMac(t.message);
+
+      assertEquals(prBytes.length, t.tag.length - 1);
+      assertArrayEquals(prBytes, Arrays.copyOf(tag, prBytes.length));
+    }
+  }
+
+  @Test
   public void testTagTruncation() throws Exception {
     for (MacTestVector t : HMAC_TEST_VECTORS) {
-      Mac mac = new MacJce(t.algName, new SecretKeySpec(t.key, "HMAC"), t.tag.length);
+      Mac mac =
+          new PrfMac(new PrfHmacJce(t.algName, new SecretKeySpec(t.key, "HMAC")), t.tag.length);
       for (int j = 1; j < t.tag.length; j++) {
         byte[] modifiedTag = Arrays.copyOf(t.tag, t.tag.length - j);
         try {
@@ -101,8 +136,10 @@
     }
     // Test with random keys.
     for (MacTestVector t : HMAC_TEST_VECTORS) {
-      Mac mac = new MacJce(
-          t.algName, new SecretKeySpec(Random.randBytes(t.key.length), "HMAC"), t.tag.length);
+      Mac mac =
+          new PrfMac(
+              new PrfHmacJce(t.algName, new SecretKeySpec(Random.randBytes(t.key.length), "HMAC")),
+              t.tag.length);
       for (int j = 1; j < t.tag.length; j++) {
         byte[] modifiedTag = Arrays.copyOf(t.tag, t.tag.length - j);
         try {
@@ -118,7 +155,8 @@
   @Test
   public void testBitFlipMessage() throws Exception {
     for (MacTestVector t : HMAC_TEST_VECTORS) {
-      Mac mac = new MacJce(t.algName, new SecretKeySpec(t.key, "HMAC"), t.tag.length);
+      Mac mac =
+          new PrfMac(new PrfHmacJce(t.algName, new SecretKeySpec(t.key, "HMAC")), t.tag.length);
       for (int b = 0; b < t.message.length; b++) {
         for (int bit = 0; bit < 8; bit++) {
           byte[] modifiedMessage = Arrays.copyOf(t.message, t.message.length);
@@ -134,8 +172,10 @@
     }
     // Test with random keys.
     for (MacTestVector t : HMAC_TEST_VECTORS) {
-      Mac mac = new MacJce(
-          t.algName, new SecretKeySpec(Random.randBytes(t.key.length), "HMAC"), t.tag.length);
+      Mac mac =
+          new PrfMac(
+              new PrfHmacJce(t.algName, new SecretKeySpec(Random.randBytes(t.key.length), "HMAC")),
+              t.tag.length);
       for (int j = 1; j < t.tag.length; j++) {
         byte[] modifiedTag = Arrays.copyOf(t.tag, t.tag.length - j);
         try {
@@ -151,7 +191,8 @@
   @Test
   public void testBitFlipTag() throws Exception {
     for (MacTestVector t : HMAC_TEST_VECTORS) {
-      Mac mac = new MacJce(t.algName, new SecretKeySpec(t.key, "HMAC"), t.tag.length);
+      Mac mac =
+          new PrfMac(new PrfHmacJce(t.algName, new SecretKeySpec(t.key, "HMAC")), t.tag.length);
       for (int b = 0; b < t.tag.length; b++) {
         for (int bit = 0; bit < 8; bit++) {
           byte[] modifiedTag = Arrays.copyOf(t.tag, t.tag.length);
@@ -167,8 +208,10 @@
     }
     // Test with random keys.
     for (MacTestVector t : HMAC_TEST_VECTORS) {
-      Mac mac = new MacJce(
-          t.algName, new SecretKeySpec(Random.randBytes(t.key.length), "HMAC"), t.tag.length);
+      Mac mac =
+          new PrfMac(
+              new PrfHmacJce(t.algName, new SecretKeySpec(Random.randBytes(t.key.length), "HMAC")),
+              t.tag.length);
       for (int b = 0; b < t.tag.length; b++) {
         for (int bit = 0; bit < 8; bit++) {
           byte[] modifiedTag = Arrays.copyOf(t.tag, t.tag.length);
@@ -187,7 +230,7 @@
   @Test
   public void testThrowExceptionIfKeySizeIsTooSmall() throws Exception {
     try {
-      new MacJce("HMACSHA1", new SecretKeySpec(Random.randBytes(15), "HMAC"), 16);
+      new PrfMac(new PrfHmacJce("HMACSHA1", new SecretKeySpec(Random.randBytes(15), "HMAC")), 16);
       fail("Expected InvalidAlgorithmParameterException");
     } catch (InvalidAlgorithmParameterException ex) {
       // expected.
@@ -201,10 +244,17 @@
     testThrowExceptionIfTagSizeIsTooSmall("HMACSHA512");
   }
 
+  @Test
+  public void testPrfAllowsSmallTagSizeCompute() throws Exception {
+    testPrfNoExceptionIfTagSizeIsTooSmall("HMACSHA1");
+    testPrfNoExceptionIfTagSizeIsTooSmall("HMACSHA256");
+    testPrfNoExceptionIfTagSizeIsTooSmall("HMACSHA512");
+  }
+
   private static void testThrowExceptionIfTagSizeIsTooSmall(String algoName) throws Exception {
-    for (int i = 0; i < MacJce.MIN_TAG_SIZE_IN_BYTES; i++) {
+    for (int i = 0; i < PrfMac.MIN_TAG_SIZE_IN_BYTES; i++) {
       try {
-        new MacJce(algoName, new SecretKeySpec(Random.randBytes(16), "HMAC"), i);
+        new PrfMac(new PrfHmacJce(algoName, new SecretKeySpec(Random.randBytes(16), "HMAC")), i);
         fail("Expected InvalidAlgorithmParameterException");
       } catch (InvalidAlgorithmParameterException ex) {
         // expected.
@@ -212,17 +262,39 @@
     }
   }
 
+  private static void testPrfNoExceptionIfTagSizeIsTooSmall(String algoName) throws Exception {
+    for (int i = 0; i < PrfMac.MIN_TAG_SIZE_IN_BYTES; i++) {
+      new PrfHmacJce(algoName, new SecretKeySpec(Random.randBytes(16), "HMAC"))
+          .compute(new byte[100], i);
+    }
+  }
+
   @Test
   public void testThrowExceptionIfTagSizeIsTooLarge() throws Exception {
     testThrowExceptionIfTagSizeIsTooLarge("HMACSHA1", 21);
     testThrowExceptionIfTagSizeIsTooLarge("HMACSHA256", 33);
     testThrowExceptionIfTagSizeIsTooLarge("HMACSHA512", 65);
+    testPrfThrowsExceptionIfTagSizeIsTooLarge("HMACSHA1", 21);
+    testPrfThrowsExceptionIfTagSizeIsTooLarge("HMACSHA256", 33);
+    testPrfThrowsExceptionIfTagSizeIsTooLarge("HMACSHA512", 65);
   }
 
   private static void testThrowExceptionIfTagSizeIsTooLarge(String algoName, int tagSize)
       throws Exception {
     try {
-      new MacJce(algoName, new SecretKeySpec(Random.randBytes(16), "HMAC"), tagSize);
+      new PrfMac(
+          new PrfHmacJce(algoName, new SecretKeySpec(Random.randBytes(16), "HMAC")), tagSize);
+      fail("Expected InvalidAlgorithmParameterException");
+    } catch (InvalidAlgorithmParameterException ex) {
+      // expected.
+    }
+  }
+
+  public void testPrfThrowsExceptionIfTagSizeIsTooLarge(String algoName, int tagSize)
+      throws Exception {
+    try {
+      Prf r = new PrfHmacJce(algoName, new SecretKeySpec(Random.randBytes(16), "HMAC"));
+      r.compute(new byte[30], tagSize);
       fail("Expected InvalidAlgorithmParameterException");
     } catch (InvalidAlgorithmParameterException ex) {
       // expected.
diff --git a/java_src/src/test/java/com/google/crypto/tink/subtle/prf/HkdfStreamingPrfTest.java b/java_src/src/test/java/com/google/crypto/tink/subtle/prf/HkdfStreamingPrfTest.java
index 655c60e..ef63a13 100644
--- a/java_src/src/test/java/com/google/crypto/tink/subtle/prf/HkdfStreamingPrfTest.java
+++ b/java_src/src/test/java/com/google/crypto/tink/subtle/prf/HkdfStreamingPrfTest.java
@@ -16,10 +16,12 @@
 import static com.google.common.truth.Truth.assertThat;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.crypto.tink.prf.Prf;
 import com.google.crypto.tink.subtle.Enums.HashType;
 import com.google.crypto.tink.subtle.Hex;
 import com.google.crypto.tink.subtle.Hkdf;
 import com.google.crypto.tink.subtle.Random;
+import com.google.crypto.tink.util.TestUtil;
 import java.io.InputStream;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -308,4 +310,18 @@
 
     assertThat(Hkdf.computeHkdf("HmacSha384", ikm, salt, info, result.length)).isEqualTo(result);
   }
+
+  @Test
+  public void testPrfUniformity() throws Exception {
+    for (int i = 0; i < HashType.values().length; i++) {
+      byte[] ikm = Random.randBytes(128);
+      byte[] salt = Random.randBytes(128);
+      byte[] message = Random.randBytes(1024);
+      Prf prf = PrfImpl.wrap(new HkdfStreamingPrf(HashType.SHA256, ikm, salt));
+      byte[] prBytes = prf.compute(message, message.length);
+      TestUtil.ztestUniformString(prBytes);
+      TestUtil.ztestAutocorrelationUniformString(prBytes);
+      TestUtil.ztestCrossCorrelationUniformStrings(prBytes, message);
+    }
+  }
 }
diff --git a/java_src/src/test/java/com/google/crypto/tink/util/TestUtilTest.java b/java_src/src/test/java/com/google/crypto/tink/util/TestUtilTest.java
new file mode 100644
index 0000000..8edda06
--- /dev/null
+++ b/java_src/src/test/java/com/google/crypto/tink/util/TestUtilTest.java
@@ -0,0 +1,177 @@
+// 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.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Tests some of the more complex functions from TestUtil. */
+@RunWith(JUnit4.class)
+public class TestUtilTest {
+  SecureRandom random;
+  final byte[] randomBytes = new byte[512];
+  // Correlated to randomBytes: derived by 1 + randomBytes.
+  final byte[] correlatedRandomBytes = new byte[512];
+  final byte[] moreRandomBytes = new byte[512];
+  final ArrayList<Integer> randomIndices = new ArrayList<>(512);
+
+  @Before
+  public void startup() throws NoSuchAlgorithmException {
+    random = SecureRandom.getInstanceStrong();
+    random.setSeed(1234);
+    random.nextBytes(randomBytes);
+    random.nextBytes(moreRandomBytes);
+
+    for (int i = 0; i < randomBytes.length; i++) {
+      correlatedRandomBytes[i] = randomBytes[i];
+      randomIndices.add(i);
+    }
+    Collections.shuffle(randomIndices);
+  }
+
+  @Test
+  public void testZTestUniformitySucceedsOnRandomData() throws GeneralSecurityException {
+    TestUtil.ztestUniformString(randomBytes);
+  }
+
+  @Test
+  public void testZTestUniformityFailsOnNonRandomData() {
+    String msg =
+        assertThrows(
+                GeneralSecurityException.class,
+                new ThrowingRunnable() {
+                  @Override
+                  public void run() throws Throwable {
+                    setValueForNBytes(randomBytes, 256, (byte) 1);
+                    TestUtil.ztestUniformString(randomBytes);
+                  }
+                })
+            .getMessage();
+    assertThat(msg).contains("Z test for uniformly distributed variable out of bounds");
+  }
+
+  @Test
+  public void testZTestUniformityFailsOnNonRandomNegativeData() {
+    String msg =
+        assertThrows(
+                GeneralSecurityException.class,
+                new ThrowingRunnable() {
+                  @Override
+                  public void run() throws Throwable {
+                    setValueForNBytes(randomBytes, 256, (byte) -120);
+                    TestUtil.ztestUniformString(randomBytes);
+                  }
+                })
+            .getMessage();
+    assertThat(msg).contains("Z test for uniformly distributed variable out of bounds");
+  }
+
+  @Test
+  public void testZTestUniformityFailsOnSmallUniformMessage() {
+    String msg =
+        assertThrows(
+                GeneralSecurityException.class,
+                new ThrowingRunnable() {
+                  @Override
+                  public void run() throws Throwable {
+                    byte[] allZeros = new byte[16];
+                    TestUtil.ztestUniformString(allZeros);
+                  }
+                })
+            .getMessage();
+    assertThat(msg).contains("Z test for uniformly distributed variable out of bounds");
+  }
+
+  @Test
+  public void testZTestUniformityFailsWithTooSmallMessage() {
+    String msg =
+        assertThrows(
+                GeneralSecurityException.class,
+                new ThrowingRunnable() {
+                  @Override
+                  public void run() throws Throwable {
+                    byte[] allZeros = new byte[4];
+                    TestUtil.ztestUniformString(allZeros);
+                  }
+                })
+            .getMessage();
+    assertThat(msg).contains("Use more bytes.");
+  }
+
+  @Test
+  public void testZTestCrossCorrelationUniformitySucceedsOnRandomData()
+      throws GeneralSecurityException {
+    TestUtil.ztestCrossCorrelationUniformStrings(randomBytes, moreRandomBytes);
+  }
+
+  @Test
+  public void testZTestCrossCorrelationUniformityFailsOnCorrelatedData()
+      throws GeneralSecurityException {
+    String msg =
+        assertThrows(
+                GeneralSecurityException.class,
+                new ThrowingRunnable() {
+                  @Override
+                  public void run() throws Throwable {
+                    TestUtil.ztestCrossCorrelationUniformStrings(
+                        randomBytes, correlatedRandomBytes);
+                  }
+                })
+            .getMessage();
+    assertThat(msg).contains("Z test for uniformly distributed variable out of bounds");
+  }
+
+  @Test
+  public void testZTestAutoCorrelationSucceedsOnRandomData() throws GeneralSecurityException {
+    TestUtil.ztestAutocorrelationUniformString(randomBytes);
+  }
+
+  @Test
+  public void testZTestAutoCorrelationFailsOnAutoCorrelatedData() throws GeneralSecurityException {
+    byte[] repeatedRandom = new byte[randomBytes.length * 3];
+    for (int i = 0; i < 3; i++) {
+      System.arraycopy(randomBytes, 0, repeatedRandom, i * randomBytes.length, randomBytes.length);
+    }
+
+    String msg =
+        assertThrows(
+                GeneralSecurityException.class,
+                new ThrowingRunnable() {
+                  @Override
+                  public void run() throws Throwable {
+                    TestUtil.ztestAutocorrelationUniformString(repeatedRandom);
+                  }
+                })
+            .getMessage();
+    assertThat(msg).contains("Z test for uniformly distributed variable out of bounds");
+  }
+
+  private void setValueForNBytes(byte[] input, int numberBytesToSet, byte valueToSet) {
+    for (int i = 0; i < numberBytesToSet; i++) {
+      input[randomIndices.get(i)] = valueToSet;
+    }
+  }
+}
diff --git a/java_src/tink_java_deps_init.bzl b/java_src/tink_java_deps_init.bzl
index 61b7f4a..9c95b78 100644
--- a/java_src/tink_java_deps_init.bzl
+++ b/java_src/tink_java_deps_init.bzl
@@ -43,8 +43,6 @@
     )
     android_sdk_repository(
         name = "androidsdk",
-        # Tink uses features in Android Keystore that are only supported at this
-        # level or newer.
-        # See https://developer.android.com/training/articles/keystore.html.
-        api_level = 23,  # M
+        # Tink uses some APIs that only supported at this level.
+        api_level = 26,  # Oreo
     )
diff --git a/java_src/tools/build_defs/tink_java_rules.bzl b/java_src/tools/build_defs/tink_java_rules.bzl
index bf235ef..8487cc8 100644
--- a/java_src/tools/build_defs/tink_java_rules.bzl
+++ b/java_src/tools/build_defs/tink_java_rules.bzl
@@ -1,51 +1,20 @@
 """Tink rules for java."""
 
-load("//tools/build_defs/android:rules.bzl", "android_binary", "android_instrumentation_test", "android_library")
-load("//devtools/build_cleaner/skylark:build_defs.bzl", "register_extension_info")
+load("//tools/build_defs/android:rules.bzl", "android_binary", "android_instrumentation_test")
 
-def tink_java_test(name, java_deps = [], android_deps = [], **kwargs):
-    """Java Test for Tink.
-
-    Creates a java_test for the sources as well as an android_library target, for which
-    tests are generated when tink_create_android_test_suite() is called.
-
-    This means that tink_create_android_test_suite must be called at some point *after*
-    tink_java_test in the BUILD file. Failing to do so is a bug.
-    """
-
-    native.java_test(name = name, deps = java_deps, **kwargs)
-
-    android_library(
-        name = name + "_android_test_library",
-        deps = android_deps,
-        testonly = 1,
-        **kwargs
-    )
-
-def _is_version_disabled(target, version_num):
-    """Returns true if the target should be disabled for this android version.
-
-    This is true if target["tags"] exists and contains a string android_min_version:xx"
-    with xx > version_num."""
-
-    if "tags" not in target:
-        return False
-    TAG_TO_STRIP = "android_min_version:"
-    min_versions = [t[len(TAG_TO_STRIP):] for t in target["tags"] if t.startswith(TAG_TO_STRIP)]
-    return any([int(v) > version_num for v in min_versions])
-
-def tink_create_android_test_suite(shard_count = 1):
-    """Creates an android test suite for previous tink_java_test.
+def collect_android_libraries_and_make_test_suite(name, shard_count = 1):
+    """Creates an android test suite for android_library in the current package.
 
     Creates, for a bunch of devices, an android_instrumentation_test which
-    runs all tests previously defined in tink_java_test, but only for
+    runs all tests previously defined in android_library, but only for
     the versions specified there.
 
-    If the tink_java_test target has a tag "android_min_version:xx", the
+    If the android_library target has a tag "android_min_version:xx", the
     corresponding test is only added to android versions xx and above.
 
     Args:
-       shard_count: the number of shards under which the resulting binary runs.
+        name: The prefix of the generated android test rules.
+        shard_count: the number of shards under which the resulting binary runs.
     """
 
     TARGET_DEVICES = {
@@ -62,33 +31,28 @@
     for version_num, device in TARGET_DEVICES.items():
         dependencies = {}
         for target_name, library_target in native.existing_rules().items():
-            if library_target["kind"] == "android_library":
-                if not _is_version_disabled(library_target, version_num):
-                    dependencies[target_name] = True
+            android_min_version = 0
+            if "tags" in library_target:
+                for tag in library_target["tags"]:
+                    if tag.startswith("android_min_version"):
+                        _, x = tag.split(":")
+                        android_min_version = int(x)
+                        break
+            if library_target["kind"] == "android_library" and android_min_version <= version_num:
+                dependencies[target_name] = True
         dependencies["//java/com/google/android/apps/common/testing/testrunner"] = True
 
-        binary_name = "android_" + str(version_num) + "_collected_binary"
+        binary_name = name + "_" + str(version_num) + "_collected_binary"
         android_binary(
             name = binary_name,
             deps = list(dependencies),
-            manifest = "//third_party/tink/javatests:AndroidManifest.xml",
+            manifest = "//third_party/tink/java_src/src/androidtest:AndroidManifest.xml",
             testonly = 1,
         )
         android_instrumentation_test(
-            name = "android_test_suite_" + str(version_num) + "_test",
+            name = name + "_" + str(version_num) + "_test",
             shard_count = shard_count,
             target_device = device,
             test_app = binary_name,
+            tags = ["manual"],
         )
-
-# Tell build_cleaner that in tink_java_test, java_deps should be the dependencies needed
-# by the created rule with the same name, android_deps should be the dependencies needed
-# by the same name concatenated with _android_test_library.
-# go/build-cleaner-build-extensions
-register_extension_info(
-    extension = tink_java_test,
-    label_regex_map = {
-        "java_deps": "deps:{extension_name}",
-        "android_deps": "deps:{extension_name}_android_test_library",
-    },
-)
diff --git a/java_src/tools/gen_maven_jar_rules.bzl b/java_src/tools/gen_maven_jar_rules.bzl
index f6b5fbc..fe402bf 100644
--- a/java_src/tools/gen_maven_jar_rules.bzl
+++ b/java_src/tools/gen_maven_jar_rules.bzl
@@ -12,6 +12,7 @@
 
 """ Definition of gen_maven_jar_rules. """
 
+load("//tools:jar_jar.bzl", "jar_jar")
 load("//tools:java_single_jar.bzl", "java_single_jar")
 load("//tools:javadoc.bzl", "javadoc_library")
 
@@ -27,7 +28,10 @@
 def gen_maven_jar_rules(
         name,
         deps = [],
+        resources = [],
         root_packages = _TINK_PACKAGES,
+        shaded_packages = [],
+        shading_rules = "",
         exclude_packages = [],
         doctitle = "",
         android_api_level = 23,
@@ -41,7 +45,14 @@
         name.jar, a source package name-src.jar and a Javadoc package
         name-javadoc.jar.
       deps: A combination of the deps of java_single_jar and javadoc_library
+      resources: A list of resource files. Files must be stored in
+        src/main/resources. Mapping rules: src/main/resources/a/b/c.txt will be
+        copied to a/b/c.txt in the output jar.
       root_packages: See javadoc_library
+      shaded_packages: These packages will be shaded, according to the rules
+        specified in shading_rules.
+      shading_rules: The shading rules, must specified when shaded_packages is present.
+        Rules file format can be found at https://github.com/bazelbuild/bazel/blob/master/third_party/jarjar/java/com/tonicsystems/jarjar/help.txt.
       exclude_packages: See javadoc_library
       doctitle: See javadoc_library
       android_api_level: See javadoc_library
@@ -49,11 +60,26 @@
       external_javadoc_links: See javadoc_library
     """
 
-    java_single_jar(
-        name = name,
-        deps = deps,
-        root_packages = root_packages,
-    )
+    if shaded_packages:
+        unshaded_jar = name + "-unshaded"
+        java_single_jar(
+            name = unshaded_jar,
+            deps = deps,
+            resources = resources,
+            root_packages = root_packages + shaded_packages,
+        )
+        jar_jar(
+            name = name,
+            input_jar = unshaded_jar,
+            rules = shading_rules,
+        )
+    else:
+        java_single_jar(
+            name = name,
+            deps = deps,
+            resources = resources,
+            root_packages = root_packages,
+        )
 
     source_jar_name = name + "-src"
     java_single_jar(
diff --git a/java_src/tools/jar_jar.bzl b/java_src/tools/jar_jar.bzl
index facc78c..418b8d6 100644
--- a/java_src/tools/jar_jar.bzl
+++ b/java_src/tools/jar_jar.bzl
@@ -9,7 +9,7 @@
 # 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.
-"""Skylark rules for jarjar. See https://github.com/pantsbuild/jarjar
+"""starlark rules for jarjar. See https://github.com/pantsbuild/jarjar
 """
 
 def _jar_jar_impl(ctx):
diff --git a/java_src/tools/java_single_jar.bzl b/java_src/tools/java_single_jar.bzl
index ad96577..bc4244d 100644
--- a/java_src/tools/java_single_jar.bzl
+++ b/java_src/tools/java_single_jar.bzl
@@ -20,12 +20,10 @@
     _check_non_empty(ctx.attr.root_packages, "root_packages")
 
     inputs = depset()
-    source_jars = []
-    for dep in ctx.attr.deps:
-        inputs = depset(transitive = [inputs, dep[JavaInfo].transitive_runtime_deps])
-        source_jars += dep[JavaInfo].transitive_source_jars.to_list()
     if ctx.attr.source_jar:
-        inputs = depset(direct = source_jars)
+        inputs = depset(transitive = [dep[JavaInfo].transitive_source_jars for dep in ctx.attr.deps])
+    else:
+        inputs = depset(transitive = [dep[JavaInfo].transitive_runtime_deps for dep in ctx.attr.deps])
 
     args = ctx.actions.args()
     args.add_all("--sources", inputs)
@@ -37,6 +35,18 @@
     args.add("--output", ctx.outputs.jar)
     args.add("--normalize")
 
+    resource_files = depset(
+        transitive = [resource.files for resource in ctx.attr.resources],
+    ).to_list()
+    args.add("--resources")
+    for resource_file in resource_files:
+        if not resource_file.path.startswith("src/main/resources"):
+            fail("resource %s must be stored in src/main/resources/" % resource_file.path)
+        relative_path = resource_file.path.replace("src/main/resources/", "")
+
+        # Map src/main/resources/a/b/c.txt to a/b/c.txt.
+        args.add(resource_file.path, format = "%s:" + relative_path)
+
     # Maybe compress code.
     if not ctx.attr.source_jar:
         # Deal with limitation of singlejar flags: tool's default behavior is
@@ -55,7 +65,7 @@
         args.add("--include_prefixes", p.replace(".", "/"))
 
     ctx.actions.run(
-        inputs = inputs,
+        inputs = inputs.to_list() + resource_files,
         outputs = [ctx.outputs.jar],
         arguments = [args],
         progress_message = "Merging into %s" % ctx.outputs.jar.short_path,
@@ -66,6 +76,10 @@
 java_single_jar = rule(
     attrs = {
         "deps": attr.label_list(providers = [JavaInfo]),
+        "resources": attr.label_list(
+            providers = [JavaInfo],
+            allow_files = True,
+        ),
         "_singlejar": attr.label(
             default = Label("@bazel_tools//tools/jdk:singlejar"),
             cfg = "host",
@@ -89,6 +103,9 @@
       exports) and runtime dependencies (runtime_deps) are collected.
       Resources are also collected. Native cc_library or java_wrap_cc
       dependencies are not.
+  resources: A combination of resource files. Files must be stored in
+      src/main/resources. Mapping rules: src/main/resources/a/b/c.txt will be
+      copied to a/b/c.txt in the output jar.
   compress: Whether to always deflate ("yes"), always store ("no"), or pass
       through unmodified ("preserve"). The default is "preserve", and is the
       most efficient option -- no extra work is done to inflate or deflate.
diff --git a/java_src/tools/javadoc.bzl b/java_src/tools/javadoc.bzl
index 7ee1293..d456513 100644
--- a/java_src/tools/javadoc.bzl
+++ b/java_src/tools/javadoc.bzl
@@ -10,7 +10,23 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-""" Definition of javadoc_library. """
+"""
+Generates a Javadoc jar path/to/target/<name>.jar.
+
+Arguments:
+  srcs: source files to process. This might contain .java files or gen_rule that
+      generates source jars.
+  deps: targets that contain references to other types referenced in Javadoc. This can be the
+      java_library/android_library target(s) for the same sources
+  root_packages: Java packages to include in generated Javadoc. Any subpackages not listed in
+      exclude_packages will be included as well
+  exclude_packages: Java packages to exclude from generated Javadoc
+  android_api_level: If Android APIs are used, the API level to compile against to generate
+      Javadoc
+  doctitle: title for Javadoc's index.html. See javadoc -doctitle
+  bottom_text: text passed to javadoc's `-bottom` flag
+  external_javadoc_links: a list of URLs that are passed to Javadoc's `-linkoffline` flag
+"""
 
 def _check_non_empty(value, name):
     if not value:
@@ -104,20 +120,3 @@
     outputs = {"jar": "%{name}.jar"},
     implementation = _javadoc_library,
 )
-"""
-Generates a Javadoc jar path/to/target/<name>.jar.
-
-Arguments:
-  srcs: source files to process. This might contain .java files or gen_rule that
-      generates source jars.
-  deps: targets that contain references to other types referenced in Javadoc. This can be the
-      java_library/android_library target(s) for the same sources
-  root_packages: Java packages to include in generated Javadoc. Any subpackages not listed in
-      exclude_packages will be included as well
-  exclude_packages: Java packages to exclude from generated Javadoc
-  android_api_level: If Android APIs are used, the API level to compile against to generate
-      Javadoc
-  doctitle: title for Javadoc's index.html. See javadoc -doctitle
-  bottom_text: text passed to javadoc's `-bottom` flag
-  external_javadoc_links: a list of URLs that are passed to Javadoc's `-linkoffline` flag
-"""
diff --git a/javascript/aead/aead_config_test.js b/javascript/aead/aead_config_test.js
index 22a5d21..6ba4441 100644
--- a/javascript/aead/aead_config_test.js
+++ b/javascript/aead/aead_config_test.js
@@ -20,9 +20,9 @@
 const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
 const AesCtrHmacAeadKeyManager = goog.require('tink.aead.AesCtrHmacAeadKeyManager');
 const AesGcmKeyManager = goog.require('tink.aead.AesGcmKeyManager');
-const KeysetHandle = goog.require('tink.KeysetHandle');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const {KeysetHandle} = goog.require('google3.third_party.tink.javascript.internal.keyset_handle');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const {PbKeyData, PbKeyStatusType, PbKeyTemplate, PbKeyset, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 describe('aead config test', function() {
diff --git a/javascript/aead/aead_wrapper.js b/javascript/aead/aead_wrapper.js
index 35bfbea..d069766 100644
--- a/javascript/aead/aead_wrapper.js
+++ b/javascript/aead/aead_wrapper.js
@@ -15,10 +15,10 @@
 goog.module('tink.aead.AeadWrapper');
 
 const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
-const CryptoFormat = goog.require('tink.CryptoFormat');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const PrimitiveWrapper = goog.require('tink.PrimitiveWrapper');
-const Registry = goog.require('tink.Registry');
+const {CryptoFormat} = goog.require('google3.third_party.tink.javascript.internal.crypto_format');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
+const {PrimitiveWrapper} = goog.require('google3.third_party.tink.javascript.internal.primitive_wrapper');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 const {PbKeyStatusType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
diff --git a/javascript/aead/aead_wrapper_test.js b/javascript/aead/aead_wrapper_test.js
index 77fa276..2401f5d 100644
--- a/javascript/aead/aead_wrapper_test.js
+++ b/javascript/aead/aead_wrapper_test.js
@@ -17,9 +17,9 @@
 
 const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
 const AeadWrapper = goog.require('tink.aead.AeadWrapper');
-const Bytes = goog.require('tink.subtle.Bytes');
-const CryptoFormat = goog.require('tink.CryptoFormat');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const {CryptoFormat} = goog.require('google3.third_party.tink.javascript.internal.crypto_format');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 const {PbKeyStatusType, PbKeysetKey, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
diff --git a/javascript/aead/aes_ctr_hmac.ts b/javascript/aead/aes_ctr_hmac.ts
index 81365f2..8e6febf 100644
--- a/javascript/aead/aes_ctr_hmac.ts
+++ b/javascript/aead/aes_ctr_hmac.ts
@@ -1,6 +1,6 @@
 import AesCtrHmacAeadKeyManager from 'goog:tink.aead.AesCtrHmacAeadKeyManager'; // from //third_party/tink/javascript/aead:aes_ctr_hmac_aead_key_manager
 import AesCtrHmacAeadKeyTemplates from 'goog:tink.aead.AesCtrHmacAeadKeyTemplates'; // from //third_party/tink/javascript/aead:aes_ctr_hmac_aead_key_templates
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
+import * as Registry from '../internal/registry';
 
 export function register() {
   Registry.registerKeyManager(new AesCtrHmacAeadKeyManager());
diff --git a/javascript/aead/aes_ctr_hmac_aead_key_manager.js b/javascript/aead/aes_ctr_hmac_aead_key_manager.js
index 968b2fd..7676bc8 100644
--- a/javascript/aead/aes_ctr_hmac_aead_key_manager.js
+++ b/javascript/aead/aes_ctr_hmac_aead_key_manager.js
@@ -15,12 +15,12 @@
 goog.module('tink.aead.AesCtrHmacAeadKeyManager');
 
 const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
-const EncryptThenAuthenticate = goog.require('tink.subtle.EncryptThenAuthenticate');
-const KeyManager = goog.require('tink.KeyManager');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const {aesCtrHmacFromRawKeys} = goog.require('google3.third_party.tink.javascript.subtle.encrypt_then_authenticate');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
+const Validators = goog.require('google3.third_party.tink.javascript.subtle.validators');
 const {PbAesCtrHmacAeadKey, PbAesCtrHmacAeadKeyFormat, PbAesCtrKey, PbAesCtrKeyFormat, PbHashType, PbHmacKey, PbHmacKeyFormat, PbKeyData} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 /**
@@ -242,7 +242,7 @@
         hashType = 'UNKNOWN HASH';
     }
 
-    const aead = await EncryptThenAuthenticate.newAesCtrHmac(
+    const aead = await aesCtrHmacFromRawKeys(
         aesCtrKey.getKeyValue_asU8(), aesCtrKey.getParams().getIvSize(),
         hashType, hmacKey.getKeyValue_asU8(), hmacKey.getParams().getTagSize());
 
diff --git a/javascript/aead/aes_ctr_hmac_aead_key_manager_test.js b/javascript/aead/aes_ctr_hmac_aead_key_manager_test.js
index ffc0ee5..d2cbff0 100644
--- a/javascript/aead/aes_ctr_hmac_aead_key_manager_test.js
+++ b/javascript/aead/aes_ctr_hmac_aead_key_manager_test.js
@@ -18,7 +18,7 @@
 const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
 const AesCtrHmacAeadKeyManager = goog.require('tink.aead.AesCtrHmacAeadKeyManager');
 const {Mac} = goog.require('google3.third_party.tink.javascript.mac.internal.mac');
-const Random = goog.require('tink.subtle.Random');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
 const {PbAesCtrHmacAeadKey, PbAesCtrHmacAeadKeyFormat, PbAesCtrKey, PbAesCtrKeyFormat, PbAesCtrParams, PbHashType, PbHmacKey, PbHmacKeyFormat, PbHmacParams, PbKeyData} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 const KEY_TYPE = 'type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey';
diff --git a/javascript/aead/aes_gcm.ts b/javascript/aead/aes_gcm.ts
index b6afa48..8529933 100644
--- a/javascript/aead/aes_gcm.ts
+++ b/javascript/aead/aes_gcm.ts
@@ -1,6 +1,6 @@
 import AesGcmKeyManager from 'goog:tink.aead.AesGcmKeyManager'; // from //third_party/tink/javascript/aead:aes_gcm_key_manager
 import AesGcmKeyTemplates from 'goog:tink.aead.AesGcmKeyTemplates'; // from //third_party/tink/javascript/aead:aes_gcm_key_templates
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
+import * as Registry from '../internal/registry';
 
 export function register() {
   Registry.registerKeyManager(new AesGcmKeyManager());
diff --git a/javascript/aead/aes_gcm_key_manager.js b/javascript/aead/aes_gcm_key_manager.js
index eb8c833..1d8c978 100644
--- a/javascript/aead/aes_gcm_key_manager.js
+++ b/javascript/aead/aes_gcm_key_manager.js
@@ -15,12 +15,12 @@
 goog.module('tink.aead.AesGcmKeyManager');
 
 const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
-const AesGcm = goog.require('tink.subtle.AesGcm');
-const KeyManager = goog.require('tink.KeyManager');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const aesGcm = goog.require('google3.third_party.tink.javascript.subtle.aes_gcm');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
+const Validators = goog.require('google3.third_party.tink.javascript.subtle.validators');
 const {PbAesGcmKey, PbAesGcmKeyFormat, PbKeyData, PbMessage} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 /**
@@ -126,7 +126,7 @@
     const keyProto = AesGcmKeyManager.getKeyProto_(key);
     AesGcmKeyManager.validateKey_(keyProto);
 
-    return await AesGcm.newInstance(keyProto.getKeyValue_asU8());
+    return await aesGcm.fromRawKey(keyProto.getKeyValue_asU8());
   }
 
   /** @override */
diff --git a/javascript/aead/aes_gcm_key_manager_test.js b/javascript/aead/aes_gcm_key_manager_test.js
index 95e78df..8e332d2 100644
--- a/javascript/aead/aes_gcm_key_manager_test.js
+++ b/javascript/aead/aes_gcm_key_manager_test.js
@@ -18,7 +18,7 @@
 const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
 const AesGcmKeyManager = goog.require('tink.aead.AesGcmKeyManager');
 const {Mac} = goog.require('google3.third_party.tink.javascript.mac.internal.mac');
-const Random = goog.require('tink.subtle.Random');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
 const {PbAesCtrKey, PbAesCtrKeyFormat, PbAesGcmKey, PbAesGcmKeyFormat, PbKeyData} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 const KEY_TYPE = 'type.googleapis.com/google.crypto.tink.AesGcmKey';
diff --git a/javascript/aead/subtle/aes_ctr_hmac.ts b/javascript/aead/subtle/aes_ctr_hmac.ts
index d78ae72..6e064f3 100644
--- a/javascript/aead/subtle/aes_ctr_hmac.ts
+++ b/javascript/aead/subtle/aes_ctr_hmac.ts
@@ -1,3 +1 @@
-import {EncryptThenAuthenticate} from './encrypt_then_authenticate';
-
-export const aesCtrHmac = EncryptThenAuthenticate.newAesCtrHmac;
+export {aesCtrHmacFromRawKeys as fromRawKeys} from '../../subtle/encrypt_then_authenticate';
diff --git a/javascript/aead/subtle/aes_gcm.ts b/javascript/aead/subtle/aes_gcm.ts
index 268b6a7..d3df615 100644
--- a/javascript/aead/subtle/aes_gcm.ts
+++ b/javascript/aead/subtle/aes_gcm.ts
@@ -1 +1 @@
-export {default as AesGcm} from 'goog:tink.subtle.AesGcm';  // from //third_party/tink/javascript/subtle:aead
+export {AesGcm, fromRawKey} from '../../subtle/aes_gcm';
diff --git a/javascript/aead/subtle/encrypt_then_authenticate.ts b/javascript/aead/subtle/encrypt_then_authenticate.ts
index 5a45f02..13ff598 100644
--- a/javascript/aead/subtle/encrypt_then_authenticate.ts
+++ b/javascript/aead/subtle/encrypt_then_authenticate.ts
@@ -1 +1 @@
-export {default as EncryptThenAuthenticate} from 'goog:tink.subtle.EncryptThenAuthenticate';  // from //third_party/tink/javascript/subtle:aead
+export {EncryptThenAuthenticate} from '../../subtle/encrypt_then_authenticate';
diff --git a/javascript/aead/subtle/index.ts b/javascript/aead/subtle/index.ts
index 618a9d2..8796fc5 100644
--- a/javascript/aead/subtle/index.ts
+++ b/javascript/aead/subtle/index.ts
@@ -1,3 +1,3 @@
-export * from './aes_ctr_hmac';
-export * from './aes_gcm';
+export {fromRawKeys as aesCtrHmac} from './aes_ctr_hmac';
+export {AesGcm, fromRawKey as aesGcmFromRawKey} from './aes_gcm';
 export * from './encrypt_then_authenticate';
diff --git a/javascript/aead/wrapper.ts b/javascript/aead/wrapper.ts
index d6382c3..9ac8258 100644
--- a/javascript/aead/wrapper.ts
+++ b/javascript/aead/wrapper.ts
@@ -1,5 +1,5 @@
 import AeadWrapper from 'goog:tink.aead.AeadWrapper'; // from //third_party/tink/javascript/aead:aead_wrapper
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
+import * as Registry from '../internal/registry';
 
 export function register() {
   Registry.registerPrimitiveWrapper(new AeadWrapper());
diff --git a/javascript/binary/index.ts b/javascript/binary/index.ts
index 2379b00..4d45e2f 100644
--- a/javascript/binary/index.ts
+++ b/javascript/binary/index.ts
@@ -1,8 +1,7 @@
-import BinaryKeysetReader from 'goog:tink.BinaryKeysetReader'; // from //third_party/tink/javascript:binary_reader
-import KeysetHandle from 'goog:tink.KeysetHandle'; // from //third_party/tink/javascript:keyset_handle_legacy
+import {BinaryKeysetReader} from '../internal/binary_keyset_reader';
+import {KeysetHandle, readNoSecret} from '../internal/keyset_handle';
 
 export function deserializeNoSecretKeyset(
     serializedKeyset: Uint8Array): KeysetHandle {
-  return KeysetHandle.readNoSecret(
-      BinaryKeysetReader.withUint8Array(serializedKeyset));
+  return readNoSecret(BinaryKeysetReader.withUint8Array(serializedKeyset));
 }
diff --git a/javascript/binary/insecure/index.ts b/javascript/binary/insecure/index.ts
index fb438de..94a9b65 100644
--- a/javascript/binary/insecure/index.ts
+++ b/javascript/binary/insecure/index.ts
@@ -1,4 +1,4 @@
-import CleartextKeysetHandle from 'goog:tink.CleartextKeysetHandle'; // from //third_party/tink/javascript:cleartext_keyset_handle
+import {CleartextKeysetHandle} from '../../internal/cleartext_keyset_handle';
 
 export const deserializeKeyset = CleartextKeysetHandle.deserializeFromBinary;
 export const serializeKeyset = CleartextKeysetHandle.serializeToBinary;
diff --git a/javascript/crypto_format.js b/javascript/crypto_format.js
deleted file mode 100644
index 5c65187..0000000
--- a/javascript/crypto_format.js
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.CryptoFormat');
-
-const {InvalidArgumentsException} = goog.require('google3.third_party.tink.javascript.exception.invalid_arguments_exception');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const {PbKeyset, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
-
-/**
- * Constants and methods that deal with the format of the outputs handled by
- * Tink.
- *
- * @static
- * @final
- */
-class CryptoFormat {
-  /**
-   * Generates the prefix for the outputs handled by the given 'key'.
-   * Throws an exception if the prefix type of 'key' is invalid.
-   *
-   * @param {PbKeyset.Key} key
-   *
-   * @return {!Uint8Array}
-   */
-  static getOutputPrefix(key) {
-    switch (key.getOutputPrefixType()) {
-      case PbOutputPrefixType.LEGACY: // fall through
-      case PbOutputPrefixType.CRUNCHY:
-        return CryptoFormat.makeOutputPrefix_(
-            key.getKeyId(), CryptoFormat.LEGACY_START_BYTE);
-      case PbOutputPrefixType.TINK:
-        return CryptoFormat.makeOutputPrefix_(
-            key.getKeyId(), CryptoFormat.TINK_START_BYTE);
-      case PbOutputPrefixType.RAW:
-        return CryptoFormat.RAW_PREFIX;
-      default:
-        throw new SecurityException('Unsupported key prefix type.');
-    }
-  }
-
-  /**
-   * Makes output prefix which consits of 4 bytes of key id in Big Endian
-   * representation followed by 1 byte of key type identifier.
-   *
-   * @static
-   * @private
-   * @param {number} keyId
-   * @param {number} keyTypeIdentifier
-   *
-   * @return {!Uint8Array}
-   */
-  static makeOutputPrefix_(keyId, keyTypeIdentifier) {
-    let /** Array */ res = [keyTypeIdentifier];
-    res = res.concat(CryptoFormat.numberAsBigEndian_(keyId));
-    return new Uint8Array(res);
-  }
-
-
-  /**
-   * Returns the given number as Uint8Array in Big Endian format.
-   *
-   * Given number has to be a non-negative integer smaller than 2^32.
-   *
-   * @static
-   * @private
-   * @param {number} n
-   *
-   * @return {!Array}
-   */
-  static numberAsBigEndian_(n) {
-    if (!Number.isInteger(n) || n < 0 || n >= 2**32) {
-      throw new InvalidArgumentsException(
-          'Number has to be unsigned 32-bit integer.');
-    }
-    const numberOfBytes = 4;
-    let res = new Array(numberOfBytes);
-    for (let i = 0; i < numberOfBytes; i++) {
-      res[i] = 0xFF & (n >> 8 * (numberOfBytes - i - 1));
-    }
-    return res;
-  }
-}
-
-/**
- * Prefix size of Tink and Legacy key types.
- * @const @static {number}
- */
-CryptoFormat.NON_RAW_PREFIX_SIZE = 5;
-
-/**
- * Prefix size of Legacy key types.
- * @const @static {number}
- */
-CryptoFormat.LEGACY_PREFIX_SIZE = CryptoFormat.NON_RAW_PREFIX_SIZE;
-/**
- * Legacy starts with 0 and is followed by 4-byte key id.
- * @const @static {number}
- */
-CryptoFormat.LEGACY_START_BYTE = 0x00;
-
-/**
- * Prefix size of Tink key types.
- * @const @static {number}
- */
-CryptoFormat.TINK_PREFIX_SIZE = CryptoFormat.NON_RAW_PREFIX_SIZE;
-/**
- * Tink starts with 1 and is followed by 4-byte key id.
- * @const @static {number}
- */
-CryptoFormat.TINK_START_BYTE = 0x01;
-
-/**
- * Raw prefix should have length 0.
- * @const @static {number}
- */
-CryptoFormat.RAW_PREFIX_SIZE = 0;
-/**
- * Raw prefix is empty Uint8Array.
- * @const @static
- * @type {!Uint8Array}
- */
-CryptoFormat.RAW_PREFIX = new Uint8Array(0);
-
-exports = CryptoFormat;
diff --git a/javascript/hybrid/decrypt_wrapper.ts b/javascript/hybrid/decrypt_wrapper.ts
index 0b7d2c6..597912b 100644
--- a/javascript/hybrid/decrypt_wrapper.ts
+++ b/javascript/hybrid/decrypt_wrapper.ts
@@ -1,5 +1,5 @@
 import HybridDecryptWrapper from 'goog:tink.hybrid.HybridDecryptWrapper'; // from //third_party/tink/javascript/hybrid:hybrid_wrappers
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
+import * as Registry from '../internal/registry';
 
 export function register() {
   Registry.registerPrimitiveWrapper(new HybridDecryptWrapper());
diff --git a/javascript/hybrid/ecies_aead_hkdf_for_decrypting.ts b/javascript/hybrid/ecies_aead_hkdf_for_decrypting.ts
index 6964d61..99cdb7c 100644
--- a/javascript/hybrid/ecies_aead_hkdf_for_decrypting.ts
+++ b/javascript/hybrid/ecies_aead_hkdf_for_decrypting.ts
@@ -1,5 +1,5 @@
 import EciesAeadHkdfPrivateKeyManager from 'goog:tink.hybrid.EciesAeadHkdfPrivateKeyManager'; // from //third_party/tink/javascript/hybrid:ecies_aead_hkdf_key_managers
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
+import * as Registry from '../internal/registry';
 
 export function register() {
   Registry.registerKeyManager(new EciesAeadHkdfPrivateKeyManager());
diff --git a/javascript/hybrid/ecies_aead_hkdf_for_encrypting.ts b/javascript/hybrid/ecies_aead_hkdf_for_encrypting.ts
index fdb9b04..e38e283 100644
--- a/javascript/hybrid/ecies_aead_hkdf_for_encrypting.ts
+++ b/javascript/hybrid/ecies_aead_hkdf_for_encrypting.ts
@@ -1,5 +1,5 @@
 import EciesAeadHkdfPublicKeyManager from 'goog:tink.hybrid.EciesAeadHkdfPublicKeyManager'; // from //third_party/tink/javascript/hybrid:ecies_aead_hkdf_key_managers
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
+import * as Registry from '../internal/registry';
 
 export function register() {
   Registry.registerKeyManager(new EciesAeadHkdfPublicKeyManager());
diff --git a/javascript/hybrid/ecies_aead_hkdf_private_key_manager.js b/javascript/hybrid/ecies_aead_hkdf_private_key_manager.js
index 92e3df8..ca68a58 100644
--- a/javascript/hybrid/ecies_aead_hkdf_private_key_manager.js
+++ b/javascript/hybrid/ecies_aead_hkdf_private_key_manager.js
@@ -14,17 +14,17 @@
 
 goog.module('tink.hybrid.EciesAeadHkdfPrivateKeyManager');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const EciesAeadHkdfHybridDecrypt = goog.require('tink.subtle.EciesAeadHkdfHybridDecrypt');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const hybridDecrypt = goog.require('google3.third_party.tink.javascript.subtle.ecies_aead_hkdf_hybrid_decrypt');
 const EciesAeadHkdfPublicKeyManager = goog.require('tink.hybrid.EciesAeadHkdfPublicKeyManager');
 const EciesAeadHkdfUtil = goog.require('tink.hybrid.EciesAeadHkdfUtil');
 const EciesAeadHkdfValidators = goog.require('tink.hybrid.EciesAeadHkdfValidators');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
 const {HybridDecrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_decrypt');
-const KeyManager = goog.require('tink.KeyManager');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
 const RegistryEciesAeadHkdfDemHelper = goog.require('tink.hybrid.RegistryEciesAeadHkdfDemHelper');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Util = goog.require('tink.Util');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbEciesAeadHkdfKeyFormat, PbEciesAeadHkdfParams, PbEciesAeadHkdfPrivateKey, PbEciesAeadHkdfPublicKey, PbKeyData, PbKeyTemplate, PbMessage} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 /**
@@ -204,7 +204,7 @@
         Util.hashTypeProtoToString(params.getKemParams().getHkdfHashType());
     const hkdfSalt = params.getKemParams().getHkdfSalt_asU8();
 
-    return await EciesAeadHkdfHybridDecrypt.newInstance(
+    return await hybridDecrypt.fromJsonWebKey(
         recepientPrivateKey, hkdfHash, pointFormat, demHelper, hkdfSalt);
   }
 
diff --git a/javascript/hybrid/ecies_aead_hkdf_private_key_manager_test.js b/javascript/hybrid/ecies_aead_hkdf_private_key_manager_test.js
index 81e8f53..5541fa6 100644
--- a/javascript/hybrid/ecies_aead_hkdf_private_key_manager_test.js
+++ b/javascript/hybrid/ecies_aead_hkdf_private_key_manager_test.js
@@ -21,9 +21,9 @@
 const EciesAeadHkdfPublicKeyManager = goog.require('tink.hybrid.EciesAeadHkdfPublicKeyManager');
 const {HybridDecrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_decrypt');
 const {HybridEncrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_encrypt');
-const KeyManager = goog.require('tink.KeyManager');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const {PbAesCtrKeyFormat, PbEciesAeadDemParams, PbEciesAeadHkdfKeyFormat, PbEciesAeadHkdfParams, PbEciesAeadHkdfPrivateKey, PbEciesAeadHkdfPublicKey, PbEciesHkdfKemParams, PbEllipticCurveType, PbHashType, PbKeyData, PbKeyTemplate, PbPointFormat} = goog.require('google3.third_party.tink.javascript.internal.proto');
 const {assertExists, assertInstanceof} = goog.require('google3.third_party.tink.javascript.testing.internal.test_utils');
 
diff --git a/javascript/hybrid/ecies_aead_hkdf_public_key_manager.js b/javascript/hybrid/ecies_aead_hkdf_public_key_manager.js
index f517fb5..625e3b1 100644
--- a/javascript/hybrid/ecies_aead_hkdf_public_key_manager.js
+++ b/javascript/hybrid/ecies_aead_hkdf_public_key_manager.js
@@ -14,14 +14,14 @@
 
 goog.module('tink.hybrid.EciesAeadHkdfPublicKeyManager');
 
-const EciesAeadHkdfHybridEncrypt = goog.require('tink.subtle.EciesAeadHkdfHybridEncrypt');
+const encrypt = goog.require('google3.third_party.tink.javascript.subtle.ecies_aead_hkdf_hybrid_encrypt');
 const EciesAeadHkdfUtil = goog.require('tink.hybrid.EciesAeadHkdfUtil');
 const EciesAeadHkdfValidators = goog.require('tink.hybrid.EciesAeadHkdfValidators');
 const {HybridEncrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_encrypt');
-const KeyManager = goog.require('tink.KeyManager');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
 const RegistryEciesAeadHkdfDemHelper = goog.require('tink.hybrid.RegistryEciesAeadHkdfDemHelper');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Util = goog.require('tink.Util');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbEciesAeadHkdfParams, PbEciesAeadHkdfPublicKey, PbKeyData, PbKeyTemplate, PbMessage} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 /**
@@ -76,7 +76,7 @@
         Util.hashTypeProtoToString(params.getKemParams().getHkdfHashType());
     const hkdfSalt = params.getKemParams().getHkdfSalt_asU8();
 
-    return await EciesAeadHkdfHybridEncrypt.newInstance(
+    return await encrypt.fromJsonWebKey(
         recepientPublicKey, hkdfHash, pointFormat, demHelper, hkdfSalt);
   }
 
diff --git a/javascript/hybrid/ecies_aead_hkdf_public_key_manager_test.js b/javascript/hybrid/ecies_aead_hkdf_public_key_manager_test.js
index 18fa8fe..36366d8 100644
--- a/javascript/hybrid/ecies_aead_hkdf_public_key_manager_test.js
+++ b/javascript/hybrid/ecies_aead_hkdf_public_key_manager_test.js
@@ -17,14 +17,14 @@
 
 const AeadConfig = goog.require('tink.aead.AeadConfig');
 const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
-const Bytes = goog.require('tink.subtle.Bytes');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
 const EciesAeadHkdfPublicKeyManager = goog.require('tink.hybrid.EciesAeadHkdfPublicKeyManager');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
 const {HybridEncrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_encrypt');
 const {Mac} = goog.require('google3.third_party.tink.javascript.mac.internal.mac');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
-const Util = goog.require('tink.Util');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbAesCtrKey, PbEciesAeadDemParams, PbEciesAeadHkdfParams, PbEciesAeadHkdfPublicKey, PbEciesHkdfKemParams, PbEllipticCurveType, PbHashType, PbKeyData, PbKeyTemplate, PbPointFormat} = goog.require('google3.third_party.tink.javascript.internal.proto');
 const {assertExists} = goog.require('google3.third_party.tink.javascript.testing.internal.test_utils');
 
diff --git a/javascript/hybrid/ecies_aead_hkdf_util.js b/javascript/hybrid/ecies_aead_hkdf_util.js
index d273248..5716e34 100644
--- a/javascript/hybrid/ecies_aead_hkdf_util.js
+++ b/javascript/hybrid/ecies_aead_hkdf_util.js
@@ -14,8 +14,8 @@
 
 goog.module('tink.hybrid.EciesAeadHkdfUtil');
 
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Util = goog.require('tink.Util');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbEciesAeadHkdfPrivateKey, PbEciesAeadHkdfPublicKey} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 // This file contains only functions which are useful for implementation of
diff --git a/javascript/hybrid/ecies_aead_hkdf_util_test.js b/javascript/hybrid/ecies_aead_hkdf_util_test.js
index 4046be3..4621bde 100644
--- a/javascript/hybrid/ecies_aead_hkdf_util_test.js
+++ b/javascript/hybrid/ecies_aead_hkdf_util_test.js
@@ -16,10 +16,10 @@
 goog.setTestOnly('tink.hybrid.EciesAeadHkdfUtilTest');
 
 const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
-const Bytes = goog.require('tink.subtle.Bytes');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
 const EciesAeadHkdfUtil = goog.require('tink.hybrid.EciesAeadHkdfUtil');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Util = goog.require('tink.Util');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbEciesAeadDemParams, PbEciesAeadHkdfParams, PbEciesAeadHkdfPrivateKey, PbEciesAeadHkdfPublicKey, PbEciesHkdfKemParams, PbEllipticCurveType, PbHashType, PbKeyTemplate, PbPointFormat} = goog.require('google3.third_party.tink.javascript.internal.proto');
 const {assertExists} = goog.require('google3.third_party.tink.javascript.testing.internal.test_utils');
 
diff --git a/javascript/hybrid/ecies_aead_hkdf_validators.js b/javascript/hybrid/ecies_aead_hkdf_validators.js
index 9e4d147..0055081 100644
--- a/javascript/hybrid/ecies_aead_hkdf_validators.js
+++ b/javascript/hybrid/ecies_aead_hkdf_validators.js
@@ -16,7 +16,7 @@
 
 const AeadConfig = goog.require('tink.aead.AeadConfig');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
+const Validators = goog.require('google3.third_party.tink.javascript.subtle.validators');
 const {PbEciesAeadDemParams, PbEciesAeadHkdfKeyFormat, PbEciesAeadHkdfParams, PbEciesAeadHkdfPrivateKey, PbEciesAeadHkdfPublicKey, PbEciesHkdfKemParams, PbEllipticCurveType, PbHashType, PbPointFormat} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 
diff --git a/javascript/hybrid/ecies_aead_hkdf_validators_test.js b/javascript/hybrid/ecies_aead_hkdf_validators_test.js
index 892c00c..c6a6c27 100644
--- a/javascript/hybrid/ecies_aead_hkdf_validators_test.js
+++ b/javascript/hybrid/ecies_aead_hkdf_validators_test.js
@@ -16,10 +16,10 @@
 goog.setTestOnly('tink.hybrid.EciesAeadHkdfValidatorsTest');
 
 const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
-const Bytes = goog.require('tink.subtle.Bytes');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
 const EciesAeadHkdfValidators = goog.require('tink.hybrid.EciesAeadHkdfValidators');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Util = goog.require('tink.Util');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbEciesAeadDemParams, PbEciesAeadHkdfKeyFormat, PbEciesAeadHkdfParams, PbEciesAeadHkdfPrivateKey, PbEciesAeadHkdfPublicKey, PbEciesHkdfKemParams, PbEllipticCurveType, PbHashType, PbKeyTemplate, PbPointFormat} = goog.require('google3.third_party.tink.javascript.internal.proto');
 const {assertExists} = goog.require('google3.third_party.tink.javascript.testing.internal.test_utils');
 
diff --git a/javascript/hybrid/encrypt_wrapper.ts b/javascript/hybrid/encrypt_wrapper.ts
index d1100d0..ff06bfe 100644
--- a/javascript/hybrid/encrypt_wrapper.ts
+++ b/javascript/hybrid/encrypt_wrapper.ts
@@ -1,5 +1,5 @@
 import HybridEncryptWrapper from 'goog:tink.hybrid.HybridEncryptWrapper'; // from //third_party/tink/javascript/hybrid:hybrid_wrappers
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
+import * as Registry from '../internal/registry';
 
 export function register() {
   Registry.registerPrimitiveWrapper(new HybridEncryptWrapper());
diff --git a/javascript/hybrid/hybrid_config.js b/javascript/hybrid/hybrid_config.js
index 1784995..ebdb0de 100644
--- a/javascript/hybrid/hybrid_config.js
+++ b/javascript/hybrid/hybrid_config.js
@@ -19,7 +19,7 @@
 const EciesAeadHkdfPublicKeyManager = goog.require('tink.hybrid.EciesAeadHkdfPublicKeyManager');
 const HybridDecryptWrapper = goog.require('tink.hybrid.HybridDecryptWrapper');
 const HybridEncryptWrapper = goog.require('tink.hybrid.HybridEncryptWrapper');
-const Registry = goog.require('tink.Registry');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 
 // Static methods and constants for registering with the Registry all instances
 // of key types for hybrid encryption and decryption supported in a particular
diff --git a/javascript/hybrid/hybrid_config_test.js b/javascript/hybrid/hybrid_config_test.js
index d59706e..81dfa00 100644
--- a/javascript/hybrid/hybrid_config_test.js
+++ b/javascript/hybrid/hybrid_config_test.js
@@ -21,9 +21,9 @@
 const {HybridDecrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_decrypt');
 const {HybridEncrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_encrypt');
 const HybridKeyTemplates = goog.require('tink.hybrid.HybridKeyTemplates');
-const KeysetHandle = goog.require('tink.KeysetHandle');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const {KeysetHandle} = goog.require('google3.third_party.tink.javascript.internal.keyset_handle');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const {PbKeyData, PbKeyStatusType, PbKeyTemplate, PbKeyset, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 describe('hybrid config test', function() {
diff --git a/javascript/hybrid/hybrid_decrypt_wrapper.js b/javascript/hybrid/hybrid_decrypt_wrapper.js
index d478744..3ebd9d5 100644
--- a/javascript/hybrid/hybrid_decrypt_wrapper.js
+++ b/javascript/hybrid/hybrid_decrypt_wrapper.js
@@ -14,10 +14,10 @@
 
 goog.module('tink.hybrid.HybridDecryptWrapper');
 
-const CryptoFormat = goog.require('tink.CryptoFormat');
+const {CryptoFormat} = goog.require('google3.third_party.tink.javascript.internal.crypto_format');
 const {HybridDecrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_decrypt');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const PrimitiveWrapper = goog.require('tink.PrimitiveWrapper');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
+const {PrimitiveWrapper} = goog.require('google3.third_party.tink.javascript.internal.primitive_wrapper');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 const {PbKeyStatusType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
diff --git a/javascript/hybrid/hybrid_decrypt_wrapper_test.js b/javascript/hybrid/hybrid_decrypt_wrapper_test.js
index b78e0b1..a9daed1 100644
--- a/javascript/hybrid/hybrid_decrypt_wrapper_test.js
+++ b/javascript/hybrid/hybrid_decrypt_wrapper_test.js
@@ -15,13 +15,13 @@
 goog.module('tink.hybrid.HybridDecryptWrapperTest');
 goog.setTestOnly('tink.hybrid.HybridDecryptWrapperTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
 const {HybridDecrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_decrypt');
 const HybridDecryptWrapper = goog.require('tink.hybrid.HybridDecryptWrapper');
 const {HybridEncrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_encrypt');
 const HybridEncryptWrapper = goog.require('tink.hybrid.HybridEncryptWrapper');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const Random = goog.require('tink.subtle.Random');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 const {PbKeyStatusType, PbKeysetKey, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
diff --git a/javascript/hybrid/hybrid_encrypt_wrapper.js b/javascript/hybrid/hybrid_encrypt_wrapper.js
index d72c73b..c8a99af 100644
--- a/javascript/hybrid/hybrid_encrypt_wrapper.js
+++ b/javascript/hybrid/hybrid_encrypt_wrapper.js
@@ -14,10 +14,10 @@
 
 goog.module('tink.hybrid.HybridEncryptWrapper');
 
-const Bytes = goog.require('tink.subtle.Bytes');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
 const {HybridEncrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_encrypt');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const PrimitiveWrapper = goog.require('tink.PrimitiveWrapper');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
+const {PrimitiveWrapper} = goog.require('google3.third_party.tink.javascript.internal.primitive_wrapper');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 
 /**
diff --git a/javascript/hybrid/hybrid_encrypt_wrapper_test.js b/javascript/hybrid/hybrid_encrypt_wrapper_test.js
index aa94837..a7d9bc7 100644
--- a/javascript/hybrid/hybrid_encrypt_wrapper_test.js
+++ b/javascript/hybrid/hybrid_encrypt_wrapper_test.js
@@ -15,11 +15,11 @@
 goog.module('tink.hybrid.HybridEncryptWrapperTest');
 goog.setTestOnly('tink.hybrid.HybridEncryptWrapperTest');
 
-const CryptoFormat = goog.require('tink.CryptoFormat');
+const {CryptoFormat} = goog.require('google3.third_party.tink.javascript.internal.crypto_format');
 const {HybridEncrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_encrypt');
 const HybridEncryptWrapper = goog.require('tink.hybrid.HybridEncryptWrapper');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const Random = goog.require('tink.subtle.Random');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
 const {PbKeyStatusType, PbKeysetKey, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 describe('hybrid encrypt wrapper test', function() {
diff --git a/javascript/hybrid/registry_ecies_aead_hkdf_dem_helper.js b/javascript/hybrid/registry_ecies_aead_hkdf_dem_helper.js
index 8a69887..69ef2de 100644
--- a/javascript/hybrid/registry_ecies_aead_hkdf_dem_helper.js
+++ b/javascript/hybrid/registry_ecies_aead_hkdf_dem_helper.js
@@ -16,8 +16,8 @@
 
 const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
 const AeadConfig = goog.require('tink.aead.AeadConfig');
-const EciesAeadHkdfDemHelper = goog.require('tink.subtle.EciesAeadHkdfDemHelper');
-const Registry = goog.require('tink.Registry');
+const {EciesAeadHkdfDemHelper} = goog.require('google3.third_party.tink.javascript.subtle.ecies_aead_hkdf_dem_helper');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 const {PbAesCtrHmacAeadKey, PbAesCtrHmacAeadKeyFormat, PbAesGcmKey, PbAesGcmKeyFormat, PbKeyTemplate} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
diff --git a/javascript/hybrid/registry_ecies_aead_hkdf_dem_helper_test.js b/javascript/hybrid/registry_ecies_aead_hkdf_dem_helper_test.js
index bb07598..26e9325 100644
--- a/javascript/hybrid/registry_ecies_aead_hkdf_dem_helper_test.js
+++ b/javascript/hybrid/registry_ecies_aead_hkdf_dem_helper_test.js
@@ -17,8 +17,8 @@
 
 const AeadConfig = goog.require('tink.aead.AeadConfig');
 const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const RegistryEciesAeadHkdfDemHelper = goog.require('tink.hybrid.RegistryEciesAeadHkdfDemHelper');
 
 describe('registry ecies aead hkdf dem helper test', function() {
diff --git a/javascript/index.ts b/javascript/index.ts
index 100ceb1..5fa44f7 100644
--- a/javascript/index.ts
+++ b/javascript/index.ts
@@ -1 +1 @@
-export * from './keyset_handle';
+export {generateNew as generateNewKeysetHandle, KeysetHandle} from './keyset_handle';
diff --git a/javascript/binary_keyset_reader.js b/javascript/internal/binary_keyset_reader.ts
similarity index 62%
rename from javascript/binary_keyset_reader.js
rename to javascript/internal/binary_keyset_reader.ts
index c98441a..a6290dd 100644
--- a/javascript/binary_keyset_reader.js
+++ b/javascript/internal/binary_keyset_reader.ts
@@ -11,32 +11,21 @@
 // limitations under the License.
 //
 ////////////////////////////////////////////////////////////////////////////////
+import {SecurityException} from '../exception/security_exception';
 
-goog.module('tink.BinaryKeysetReader');
-
-const KeysetReader = goog.require('tink.KeysetReader');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const {PbKeyset} = goog.require('google3.third_party.tink.javascript.internal.proto');
+import {KeysetReader} from './keyset_reader';
+import {PbEncryptedKeyset, PbKeyset} from './proto';
 
 /**
  * BinaryKeysetReader knows how to read a keyset or an encrypted keyset
  * serialized to binary format.
  *
- * @implements {KeysetReader}
  * @final
  */
-class BinaryKeysetReader {
-  /** @param {!Uint8Array} serializedKeyset */
-  constructor(serializedKeyset) {
-    /** @const @private {!Uint8Array} */
-    this.serializedKeyset_ = serializedKeyset;
-  }
+export class BinaryKeysetReader implements KeysetReader {
+  constructor(private readonly serializedKeyset: Uint8Array) {}
 
-  /**
-   * @param {!Uint8Array} serializedKeyset
-   * @return {!BinaryKeysetReader}
-   */
-  static withUint8Array(serializedKeyset) {
+  static withUint8Array(serializedKeyset: Uint8Array): BinaryKeysetReader {
     if (!serializedKeyset) {
       throw new SecurityException('Serialized keyset has to be non-null.');
     }
@@ -45,9 +34,9 @@
 
   /** @override */
   read() {
-    let /** !PbKeyset */ keyset;
+    let keyset: PbKeyset;
     try {
-      keyset = PbKeyset.deserializeBinary(this.serializedKeyset_);
+      keyset = PbKeyset.deserializeBinary(this.serializedKeyset);
     } catch (e) {
       throw new SecurityException(
           'Could not parse the given serialized proto as a keyset proto.');
@@ -60,9 +49,7 @@
   }
 
   /** @override */
-  readEncrypted() {
+  readEncrypted(): PbEncryptedKeyset {
     throw new SecurityException('Not implemented yet.');
   }
 }
-
-exports = BinaryKeysetReader;
diff --git a/javascript/binary_keyset_reader_test.js b/javascript/internal/binary_keyset_reader_test.js
similarity index 95%
rename from javascript/binary_keyset_reader_test.js
rename to javascript/internal/binary_keyset_reader_test.js
index 0ca8e3b..ee9f505 100644
--- a/javascript/binary_keyset_reader_test.js
+++ b/javascript/internal/binary_keyset_reader_test.js
@@ -15,8 +15,8 @@
 goog.module('tink.BinaryKeysetReaderTest');
 goog.setTestOnly('tink.BinaryKeysetReaderTest');
 
-const BinaryKeysetReader = goog.require('tink.BinaryKeysetReader');
-const Random = goog.require('tink.subtle.Random');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const {BinaryKeysetReader} = goog.require('google3.third_party.tink.javascript.internal.binary_keyset_reader');
 const {PbKeyData, PbKeyStatusType, PbKeyset, PbKeysetKey, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 describe('binary keyset reader test', function() {
diff --git a/javascript/binary_keyset_writer.js b/javascript/internal/binary_keyset_writer.ts
similarity index 73%
rename from javascript/binary_keyset_writer.js
rename to javascript/internal/binary_keyset_writer.ts
index 34867d7..dc7e256 100644
--- a/javascript/binary_keyset_writer.js
+++ b/javascript/internal/binary_keyset_writer.ts
@@ -11,27 +11,20 @@
 // limitations under the License.
 //
 ////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.BinaryKeysetWriter');
-
-const KeysetWriter = goog.require('tink.KeysetWriter');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-
+import {SecurityException} from '../exception/security_exception';
+import {KeysetWriter} from './keyset_writer';
 
 /**
  * KeysetWriter knows how to write a keyset or an encrypted keyset.
  *
- * @implements {KeysetWriter}
  * @final
  */
-class BinaryKeysetWriter {
+export class BinaryKeysetWriter implements KeysetWriter {
   /** @override */
-  write(keyset) {
+  write(keyset: AnyDuringMigration) {
     if (!keyset) {
       throw new SecurityException('keyset has to be non-null.');
     }
     return keyset.serializeBinary();
   }
 }
-
-exports = BinaryKeysetWriter;
diff --git a/javascript/binary_keyset_writer_test.js b/javascript/internal/binary_keyset_writer_test.js
similarity index 85%
rename from javascript/binary_keyset_writer_test.js
rename to javascript/internal/binary_keyset_writer_test.js
index 1526ef9..cd4a300 100644
--- a/javascript/binary_keyset_writer_test.js
+++ b/javascript/internal/binary_keyset_writer_test.js
@@ -15,8 +15,8 @@
 goog.module('tink.BinaryKeysetWriterTest');
 goog.setTestOnly('tink.BinaryKeysetWriterTest');
 
-const BinaryKeysetReader = goog.require('tink.BinaryKeysetReader');
-const BinaryKeysetWriter = goog.require('tink.BinaryKeysetWriter');
+const {BinaryKeysetReader} = goog.require('google3.third_party.tink.javascript.internal.binary_keyset_reader');
+const {BinaryKeysetWriter} = goog.require('google3.third_party.tink.javascript.internal.binary_keyset_writer');
 const {createKeyset} = goog.require('google3.third_party.tink.javascript.testing.internal.test_utils');
 
 describe('binary keyset writer test', function() {
diff --git a/javascript/cleartext_keyset_handle.js b/javascript/internal/cleartext_keyset_handle.ts
similarity index 65%
rename from javascript/cleartext_keyset_handle.js
rename to javascript/internal/cleartext_keyset_handle.ts
index bcd3463..d44c1a7 100644
--- a/javascript/cleartext_keyset_handle.js
+++ b/javascript/internal/cleartext_keyset_handle.ts
@@ -11,15 +11,11 @@
 // limitations under the License.
 //
 ////////////////////////////////////////////////////////////////////////////////
+import {BinaryKeysetReader} from './binary_keyset_reader';
+import {BinaryKeysetWriter} from './binary_keyset_writer';
+import {KeysetHandle} from './keyset_handle';
+import {PbKeyset} from './proto';
 
-goog.module('tink.CleartextKeysetHandle');
-
-const BinaryKeysetReader = goog.require('tink.BinaryKeysetReader');
-const BinaryKeysetWriter = goog.require('tink.BinaryKeysetWriter');
-const KeysetHandle = goog.require('tink.KeysetHandle');
-const {PbKeyset} = goog.require('google3.third_party.tink.javascript.internal.proto');
-
-/** @type {!BinaryKeysetWriter} */
 const binaryKeysetWriter = new BinaryKeysetWriter();
 
 /**
@@ -27,7 +23,7 @@
  *
  * @final
  */
-class CleartextKeysetHandle {
+export class CleartextKeysetHandle {
   /**
    * Creates a KeysetHandle from a JSPB array representation of a keyset. The
    * array is used in place and not cloned.
@@ -35,10 +31,8 @@
    * Note that JSPB is currently not open source, so this method can't be
    * either.
    *
-   * @param {!Array<*>} keysetJspbArray
-   * @return {!KeysetHandle}
    */
-  static fromJspbArray(keysetJspbArray) {
+  static fromJspbArray(keysetJspbArray: AnyDuringMigration[]): KeysetHandle {
     return new KeysetHandle(new PbKeyset(keysetJspbArray));
   }
 
@@ -48,10 +42,8 @@
    * Note that JSPB is currently not open source, so this method can't be
    * either.
    *
-   * @param {string} keysetJspbString
-   * @return {!KeysetHandle}
    */
-  static deserializeFromJspb(keysetJspbString) {
+  static deserializeFromJspb(keysetJspbString: string): KeysetHandle {
     return new KeysetHandle(PbKeyset.deserialize(keysetJspbString));
   }
 
@@ -61,34 +53,26 @@
    * Note that JSPB is currently not open source, so this method can't be
    * either.
    *
-   * @param {!KeysetHandle} keysetHandle
-   * @return {string}
    */
-  static serializeToJspb(keysetHandle) {
+  static serializeToJspb(keysetHandle: KeysetHandle): string {
     return keysetHandle.getKeyset().serialize();
   }
 
   /**
    * Serializes a KeysetHandle to binary.
    *
-   * @param {!KeysetHandle} keysetHandle
-   * @return {!Uint8Array}
    */
-  static serializeToBinary(keysetHandle) {
+  static serializeToBinary(keysetHandle: KeysetHandle): Uint8Array {
     return binaryKeysetWriter.write(keysetHandle.getKeyset());
   }
 
   /**
    * Creates a KeysetHandle from a binary representation of a keyset.
    *
-   * @param {!Uint8Array} keysetBinary
-   * @return {!KeysetHandle}
    */
-  static deserializeFromBinary(keysetBinary) {
+  static deserializeFromBinary(keysetBinary: Uint8Array): KeysetHandle {
     const reader = BinaryKeysetReader.withUint8Array(keysetBinary);
     const keysetFromReader = reader.read();
     return new KeysetHandle(keysetFromReader);
   }
 }
-
-exports = CleartextKeysetHandle;
diff --git a/javascript/cleartext_keyset_handle_test.js b/javascript/internal/cleartext_keyset_handle_test.js
similarity index 92%
rename from javascript/cleartext_keyset_handle_test.js
rename to javascript/internal/cleartext_keyset_handle_test.js
index f9ba9ac..b8cd34d 100644
--- a/javascript/cleartext_keyset_handle_test.js
+++ b/javascript/internal/cleartext_keyset_handle_test.js
@@ -15,8 +15,8 @@
 goog.module('tink.CleartextKeysetHandleTest');
 goog.setTestOnly();
 
-const CleartextKeysetHandle = goog.require('tink.CleartextKeysetHandle');
-const KeysetHandle = goog.require('tink.KeysetHandle');
+const {CleartextKeysetHandle} = goog.require('google3.third_party.tink.javascript.internal.cleartext_keyset_handle');
+const {KeysetHandle} = goog.require('google3.third_party.tink.javascript.internal.keyset_handle');
 const {PbKeyset} = goog.require('google3.third_party.tink.javascript.internal.proto');
 const {createKeyset} = goog.require('google3.third_party.tink.javascript.testing.internal.test_utils');
 
diff --git a/javascript/internal/crypto_format.ts b/javascript/internal/crypto_format.ts
new file mode 100644
index 0000000..c513f4a
--- /dev/null
+++ b/javascript/internal/crypto_format.ts
@@ -0,0 +1,119 @@
+// 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.
+//
+////////////////////////////////////////////////////////////////////////////////
+import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
+import {SecurityException} from '../exception/security_exception';
+import {PbKeyset, PbOutputPrefixType} from './proto';
+
+/**
+ * Constants and methods that deal with the format of the outputs handled by
+ * Tink.
+ *
+ * @static
+ * @final
+ */
+export class CryptoFormat {
+  /**
+   * Generates the prefix for the outputs handled by the given 'key'.
+   * Throws an exception if the prefix type of 'key' is invalid.
+   *
+   *
+   */
+  static getOutputPrefix(key: PbKeyset.Key): Uint8Array {
+    switch (key.getOutputPrefixType()) {
+      case PbOutputPrefixType.LEGACY:
+
+      // fall through
+      case PbOutputPrefixType.CRUNCHY:
+        return CryptoFormat.makeOutputPrefix_(
+            key.getKeyId(), CryptoFormat.LEGACY_START_BYTE);
+      case PbOutputPrefixType.TINK:
+        return CryptoFormat.makeOutputPrefix_(
+            key.getKeyId(), CryptoFormat.TINK_START_BYTE);
+      case PbOutputPrefixType.RAW:
+        return CryptoFormat.RAW_PREFIX;
+      default:
+        throw new SecurityException('Unsupported key prefix type.');
+    }
+  }
+
+  /**
+   * Makes output prefix which consits of 4 bytes of key id in Big Endian
+   * representation followed by 1 byte of key type identifier.
+   *
+   * @static
+   *
+   */
+  private static makeOutputPrefix_(keyId: number, keyTypeIdentifier: number):
+      Uint8Array {
+    let res: AnyDuringMigration[] = [keyTypeIdentifier];
+    res = res.concat(CryptoFormat.numberAsBigEndian_(keyId));
+    return new Uint8Array(res);
+  }
+
+  /**
+   * Returns the given number as Uint8Array in Big Endian format.
+   *
+   * Given number has to be a non-negative integer smaller than 2^32.
+   *
+   * @static
+   *
+   */
+  private static numberAsBigEndian_(n: number): AnyDuringMigration[] {
+    if (!Number.isInteger(n) || n < 0 || n >= 2 ** 32) {
+      throw new InvalidArgumentsException(
+          'Number has to be unsigned 32-bit integer.');
+    }
+    const numberOfBytes = 4;
+    const res = new Array(numberOfBytes);
+    for (let i = 0; i < numberOfBytes; i++) {
+      res[i] = 255 & n >> 8 * (numberOfBytes - i - 1);
+    }
+    return res;
+  }
+
+  /**
+   * Prefix size of Tink and Legacy key types.
+   */
+  static readonly NON_RAW_PREFIX_SIZE = 5;
+
+  /**
+   * Prefix size of Legacy key types.
+   */
+  static readonly LEGACY_PREFIX_SIZE = CryptoFormat.NON_RAW_PREFIX_SIZE;
+
+  /**
+   * Legacy starts with 0 and is followed by 4-byte key id.
+   */
+  static readonly LEGACY_START_BYTE = 0;
+
+  /**
+   * Prefix size of Tink key types.
+   */
+  static readonly TINK_PREFIX_SIZE = CryptoFormat.NON_RAW_PREFIX_SIZE;
+
+  /**
+   * Tink starts with 1 and is followed by 4-byte key id.
+   */
+  static readonly TINK_START_BYTE = 1;
+
+  /**
+   * Raw prefix should have length 0.
+   */
+  static readonly RAW_PREFIX_SIZE = 0;
+
+  /**
+   * Raw prefix is empty Uint8Array.
+   */
+  static readonly RAW_PREFIX = new Uint8Array(0);
+}
diff --git a/javascript/crypto_format_test.js b/javascript/internal/crypto_format_test.js
similarity index 96%
rename from javascript/crypto_format_test.js
rename to javascript/internal/crypto_format_test.js
index 7d116be..1e69ef6 100644
--- a/javascript/crypto_format_test.js
+++ b/javascript/internal/crypto_format_test.js
@@ -15,7 +15,7 @@
 goog.module('tink.CryptoFormatTest');
 goog.setTestOnly('tink.CryptoFormatTest');
 
-const CryptoFormat = goog.require('tink.CryptoFormat');
+const {CryptoFormat} = goog.require('google3.third_party.tink.javascript.internal.crypto_format');
 const {PbKeysetKey: PbKey, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 describe('crypto format test', function() {
diff --git a/javascript/key_manager.js b/javascript/internal/key_manager.ts
similarity index 66%
rename from javascript/key_manager.js
rename to javascript/internal/key_manager.ts
index 70fceec..40b079b 100644
--- a/javascript/key_manager.js
+++ b/javascript/internal/key_manager.ts
@@ -11,50 +11,38 @@
 // limitations under the License.
 //
 ////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.KeyManager');
-
-const {PbKeyData, PbMessage} = goog.require('google3.third_party.tink.javascript.internal.proto');
+import {PbKeyData, PbMessage} from './proto';
 
 /**
  * An auxiliary container for methods that generate new keys.
  * Those methods are separate from KeyManager as their functionality is
  * independent of the primitive of the corresponding KeyManager.
  *
- * @record
  */
-class KeyFactory {
+export interface KeyFactory {
   /**
    * Generates a new random key according to 'keyFormat'.
    *
-   * @param {!PbMessage|!Uint8Array} keyFormat is either a KeyFormat
+   * @param keyFormat is either a KeyFormat
    *     proto or a serialized KeyFormat proto
-   * @return {!PbMessage|!Promise<!PbMessage>} the new generated key
+   * @return the new generated key
    */
-  newKey(keyFormat) {}
+  newKey(keyFormat: PbMessage|Uint8Array): PbMessage|Promise<PbMessage>;
 
   /**
    * Generates a new random key based on the "serialized_key_format" and returns
    * it as a KeyData proto.
    *
-   * @param {!Uint8Array} serializedKeyFormat
-   * @return {!PbKeyData|!Promise<!PbKeyData>}
    */
-  newKeyData(serializedKeyFormat) {}
+  newKeyData(serializedKeyFormat: Uint8Array): PbKeyData|Promise<PbKeyData>;
 }
 
-/**
- * @record
- * @extends {KeyFactory}
- */
-class PrivateKeyFactory {
+export interface PrivateKeyFactory extends KeyFactory {
   /**
    * Returns a public key data extracted from the given serialized private key.
    *
-   * @param {!Uint8Array} serializedPrivateKey
-   * @return {!PbKeyData}
    */
-  getPublicKeyData(serializedPrivateKey) {}
+  getPublicKeyData(serializedPrivateKey: Uint8Array): PbKeyData;
 }
 
 /**
@@ -66,35 +54,28 @@
  *
  * The template parameter P denotes the primitive corresponding to the keys
  * handled by this manager.
- *
- * @template P
- * @record
  */
-class KeyManager {
+export interface KeyManager<P> {
   /**
    * Constructs an instance of primitive P for a given key.
    *
-   * @param {!Object} primitiveType
-   * @param {!PbKeyData|!PbMessage} key is either a KeyData proto or a supported
+   * @param key is either a KeyData proto or a supported
    *     key proto
-   * @return {!Promise.<!P>}
    */
-  getPrimitive(primitiveType, key) {}
+  getPrimitive(primitiveType: AnyDuringMigration, key: PbKeyData|PbMessage):
+      Promise<P>;
 
   /**
    * Returns true if this KeyManager supports keyType.
    *
-   * @param {string} keyType
-   * @return {boolean}
    */
-  doesSupport(keyType) {}
+  doesSupport(keyType: string): boolean;
 
   /**
    * Returns the URL which identifies the keys managed by this KeyManager.
    *
-   * @return {string}
    */
-  getKeyType() {}
+  getKeyType(): string;
 
   /**
    * Returns the type of primitive which can be generated by this KeyManager.
@@ -103,28 +84,19 @@
    * the primitive returned by getPrimitive function implements certain
    * primitive interface (e.g. that the primitive is AEAD).
    *
-   * @return {!Object}
    */
-  getPrimitiveType() {}
+  getPrimitiveType(): AnyDuringMigration;
 
   /**
    * Returns the version of this KeyManager.
    *
-   * @return {number}
    */
-  getVersion() {}
+  getVersion(): number;
 
   /**
    * Returns a factory that generates keys of the key type handled by this
    * manager.
    *
-   * @return {!KeyFactory}
    */
-  getKeyFactory() {}
+  getKeyFactory(): KeyFactory;
 }
-
-exports = {
-  KeyManager,
-  KeyFactory,
-  PrivateKeyFactory
-};
diff --git a/javascript/internal/keyset_handle.ts b/javascript/internal/keyset_handle.ts
new file mode 100644
index 0000000..4bcdf4d
--- /dev/null
+++ b/javascript/internal/keyset_handle.ts
@@ -0,0 +1,206 @@
+// 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.
+//
+////////////////////////////////////////////////////////////////////////////////
+import {Aead} from '../aead/internal/aead';
+import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
+import {SecurityException} from '../exception/security_exception';
+import * as Random from '../subtle/random';
+
+import * as KeyManager from './key_manager';
+import {KeysetReader} from './keyset_reader';
+import {KeysetWriter} from './keyset_writer';
+import * as PrimitiveSet from './primitive_set';
+import {PbKeyMaterialType, PbKeyset, PbKeyStatusType, PbKeyTemplate} from './proto';
+import * as Registry from './registry';
+import * as Util from './util';
+
+/**
+ * Keyset handle provide abstracted access to Keysets, to limit the exposure of
+ * actual protocol buffers that hold sensitive key material.
+ *
+ * @final
+ */
+export class KeysetHandle {
+  private readonly keyset_: PbKeyset;
+
+  constructor(keyset: PbKeyset) {
+    Util.validateKeyset(keyset);
+    this.keyset_ = keyset;
+  }
+
+  /**
+   * Returns a primitive that uses key material from this keyset handle. If
+   * opt_customKeyManager is defined then the provided key manager is used to
+   * instantiate primitives. Otherwise key manager from Registry is used.
+   */
+  async getPrimitive<P>(
+      primitiveType: Util.Constructor<P>,
+      opt_customKeyManager?: KeyManager.KeyManager<P>|null): Promise<P> {
+    if (!primitiveType) {
+      throw new InvalidArgumentsException('primitive type must be non-null');
+    }
+    const primitiveSet =
+        await this.getPrimitiveSet(primitiveType, opt_customKeyManager);
+    return Registry.wrap(primitiveSet);
+  }
+
+  /**
+   * Creates a set of primitives corresponding to the keys with status Enabled
+   * in the given keysetHandle, assuming all the correspoding key managers are
+   * present (keys with status different from Enabled are skipped). If provided
+   * uses customKeyManager instead of registered key managers for keys supported
+   * by the customKeyManager.
+   *
+   * Visible for testing.
+   */
+  async getPrimitiveSet<P>(
+      primitiveType: Util.Constructor<P>,
+      opt_customKeyManager?: KeyManager.KeyManager<P>|
+      null): Promise<PrimitiveSet.PrimitiveSet<P>> {
+    const primitiveSet = new PrimitiveSet.PrimitiveSet<P>(primitiveType);
+    const keys = this.keyset_.getKeyList();
+    const keysLength = keys.length;
+    for (let i = 0; i < keysLength; i++) {
+      const key = keys[i];
+      if (key.getStatus() === PbKeyStatusType.ENABLED) {
+        const keyData = key.getKeyData();
+        if (!keyData) {
+          throw new SecurityException('Key data has to be non null.');
+        }
+        let primitive;
+        if (opt_customKeyManager &&
+            opt_customKeyManager.getKeyType() === keyData.getTypeUrl()) {
+          primitive =
+              await opt_customKeyManager.getPrimitive(primitiveType, keyData);
+        } else {
+          primitive = await Registry.getPrimitive<P>(primitiveType, keyData);
+        }
+        const entry = primitiveSet.addPrimitive(primitive, key);
+        if (key.getKeyId() === this.keyset_.getPrimaryKeyId()) {
+          primitiveSet.setPrimary(entry);
+        }
+      }
+    }
+    return primitiveSet;
+  }
+
+  /**
+   * Encrypts the underlying keyset with the provided masterKeyAead wnd writes
+   * the resulting encryptedKeyset to the given writer which must be non-null.
+   *
+   *
+   */
+  async write(writer: KeysetWriter, masterKeyAead: Aead) {
+    // TODO implement
+    throw new SecurityException('KeysetHandle -- write: Not implemented yet.');
+  }
+
+  /**
+   * Returns the keyset held by this KeysetHandle.
+   *
+   */
+  getKeyset(): PbKeyset {
+    return this.keyset_;
+  }
+}
+
+/**
+ * Creates a KeysetHandle from an encrypted keyset obtained via reader, using
+ * masterKeyAead to decrypt the keyset.
+ *
+ *
+ */
+export async function read(
+    reader: KeysetReader, masterKeyAead: Aead): Promise<KeysetHandle> {
+  // TODO implement
+  throw new SecurityException('KeysetHandle -- read: Not implemented yet.');
+}
+
+/**
+ * Returns a new KeysetHandle that contains a single new key generated
+ * according to keyTemplate.
+ *
+ *
+ */
+export async function generateNew(keyTemplate: PbKeyTemplate):
+    Promise<KeysetHandle> {
+  // TODO(thaidn): move this to a key manager.
+  const keyset = await generateNewKeyset_(keyTemplate);
+  return new KeysetHandle(keyset);
+}
+
+/**
+ * Generates a new Keyset that contains a single new key generated
+ * according to keyTemplate.
+ *
+ */
+async function generateNewKeyset_(keyTemplate: PbKeyTemplate):
+    Promise<PbKeyset> {
+  const key = (new PbKeyset.Key())
+                  .setStatus(PbKeyStatusType.ENABLED)
+                  .setOutputPrefixType(keyTemplate.getOutputPrefixType());
+  const keyId = generateNewKeyId_();
+  key.setKeyId(keyId);
+  const keyData = await Registry.newKeyData(keyTemplate);
+  key.setKeyData(keyData);
+  const keyset = new PbKeyset();
+  keyset.addKey(key);
+  keyset.setPrimaryKeyId(keyId);
+  return keyset;
+}
+
+/**
+ * Generates a new random key ID.
+ *
+ * @return The key ID.
+ */
+function generateNewKeyId_(): number {
+  const bytes = Random.randBytes(4);
+  let value = 0;
+  for (let i = 0; i < bytes.length; i++) {
+    value += (bytes[i] & 255) << i * 8;
+  }
+
+  // Make sure the key ID is a positive integer smaller than 2^32.
+  return Math.abs(value) % 2 ** 32;
+}
+
+/**
+ * Creates a KeysetHandle from a keyset, obtained via reader, which
+ * must contain no secret key material.
+ *
+ * This can be used to load public keysets or envelope encryption keysets.
+ * Users that need to load cleartext keysets can use CleartextKeysetHandle.
+ *
+ */
+export function readNoSecret(reader: KeysetReader): KeysetHandle {
+  if (reader === null) {
+    throw new SecurityException('Reader has to be non-null.');
+  }
+  const keyset = reader.read();
+  const keyList = keyset.getKeyList();
+  for (const key of keyList) {
+    const keyData = key.getKeyData();
+    if (keyData) {
+      switch (keyData.getKeyMaterialType()) {
+        case PbKeyMaterialType.ASYMMETRIC_PUBLIC:
+
+        // fall through
+        case PbKeyMaterialType.REMOTE:
+          continue;
+      }
+    }
+    throw new SecurityException('Keyset contains secret key material.');
+  }
+  return new KeysetHandle(keyset);
+}
diff --git a/javascript/keyset_handle_test.js b/javascript/internal/keyset_handle_test.js
similarity index 96%
rename from javascript/keyset_handle_test.js
rename to javascript/internal/keyset_handle_test.js
index 14be702..ca18839 100644
--- a/javascript/keyset_handle_test.js
+++ b/javascript/internal/keyset_handle_test.js
@@ -15,22 +15,22 @@
 goog.module('tink.KeysetHandleTest');
 goog.setTestOnly('tink.KeysetHandleTest');
 
-const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
-const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
-const BinaryKeysetReader = goog.require('tink.BinaryKeysetReader');
-const BinaryKeysetWriter = goog.require('tink.BinaryKeysetWriter');
-const Bytes = goog.require('tink.subtle.Bytes');
-const CleartextKeysetHandle = goog.require('tink.CleartextKeysetHandle');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
 const HybridConfig = goog.require('tink.hybrid.HybridConfig');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
+const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
+const {BinaryKeysetReader} = goog.require('google3.third_party.tink.javascript.internal.binary_keyset_reader');
+const {BinaryKeysetWriter} = goog.require('google3.third_party.tink.javascript.internal.binary_keyset_writer');
+const {CleartextKeysetHandle} = goog.require('google3.third_party.tink.javascript.internal.cleartext_keyset_handle');
 const {HybridDecrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_decrypt');
 const {HybridEncrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_encrypt');
-const KeyManager = goog.require('tink.KeyManager');
-const KeysetHandle = goog.require('tink.KeysetHandle');
+const {KeysetHandle, generateNew, read, readNoSecret} = goog.require('google3.third_party.tink.javascript.internal.keyset_handle');
 const {Mac} = goog.require('google3.third_party.tink.javascript.mac.internal.mac');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 const {PbKeyData, PbKeyMaterialType, PbKeyStatusType, PbKeyset, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
+const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 const {createKeyset} = goog.require('google3.third_party.tink.javascript.testing.internal.test_utils');
 
 describe('keyset handle test', function() {
@@ -83,13 +83,13 @@
   // tests for read method
   it('read', async function() {
     const keyTemplate = AeadKeyTemplates.aes128CtrHmacSha256();
-    const keysetHandle = await KeysetHandle.generateNew(keyTemplate);
+    const keysetHandle = await generateNew(keyTemplate);
     const serializedKeyset =
         CleartextKeysetHandle.serializeToBinary(keysetHandle);
     const keysetReader = new BinaryKeysetReader(serializedKeyset);
     const aead = await keysetHandle.getPrimitive(Aead);
     try {
-      await KeysetHandle.read(keysetReader, aead);
+      await read(keysetReader, aead);
     } catch (e) {
       expect(e.toString())
           .toBe(
@@ -103,7 +103,7 @@
   // tests for generateNew method
   it('generate new', async function() {
     const keyTemplate = AeadKeyTemplates.aes128CtrHmacSha256();
-    const keysetHandle = await KeysetHandle.generateNew(keyTemplate);
+    const keysetHandle = await generateNew(keyTemplate);
     const keyset = keysetHandle.getKeyset();
     expect(1).toBe(keyset.getKeyList().length);
 
@@ -528,7 +528,7 @@
       const reader =
           BinaryKeysetReader.withUint8Array(keyset.serializeBinary());
       try {
-        KeysetHandle.readNoSecret(reader);
+        readNoSecret(reader);
         fail('An exception should be thrown.');
       } catch (e) {
         expect(e.toString())
@@ -552,7 +552,7 @@
     keyset.setPrimaryKeyId(1);
 
     const reader = BinaryKeysetReader.withUint8Array(keyset.serializeBinary());
-    const keysetHandle = KeysetHandle.readNoSecret(reader);
+    const keysetHandle = readNoSecret(reader);
 
     expect(keysetHandle.getKeyset()).toEqual(keyset);
   });
diff --git a/javascript/keyset_reader.js b/javascript/internal/keyset_reader.ts
similarity index 75%
rename from javascript/keyset_reader.js
rename to javascript/internal/keyset_reader.ts
index 3fe9ac3..245dbdb 100644
--- a/javascript/keyset_reader.js
+++ b/javascript/internal/keyset_reader.ts
@@ -11,31 +11,23 @@
 // limitations under the License.
 //
 ////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.KeysetReader');
-
-const {PbEncryptedKeyset, PbKeyset} = goog.require('google3.third_party.tink.javascript.internal.proto');
+import {PbEncryptedKeyset, PbKeyset} from './proto';
 
 /**
  * KeysetReader knows how to read a keyset or an encrypted keyset from some
  * source.
  *
- * @record
  */
-class KeysetReader {
+export interface KeysetReader {
   /**
    * Reads and returns a (cleartext) Keyset object from the underlying source.
    *
-   * @return {!PbKeyset}
    */
-  read() {}
+  read(): PbKeyset;
 
   /**
    * Reads and returns an EncryptedKeyset from the underlying source.
    *
-   * @return {!PbEncryptedKeyset}
    */
-  readEncrypted() {}
+  readEncrypted(): PbEncryptedKeyset;
 }
-
-exports = KeysetReader;
diff --git a/javascript/keyset_writer.js b/javascript/internal/keyset_writer.ts
similarity index 70%
rename from javascript/keyset_writer.js
rename to javascript/internal/keyset_writer.ts
index cc715df..ffd461c 100644
--- a/javascript/keyset_writer.js
+++ b/javascript/internal/keyset_writer.ts
@@ -11,23 +11,13 @@
 // limitations under the License.
 //
 ////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.KeysetWriter');
-
-const {PbEncryptedKeyset, PbKeyset} = goog.require('google3.third_party.tink.javascript.internal.proto');
+import {PbEncryptedKeyset, PbKeyset} from './proto';
 
 /**
  * KeysetWriter knows how to write a keyset or an encrypted keyset to some
  * storage system.
  *
- * @record
  */
-class KeysetWriter {
-  /**
-   * @param {!PbKeyset|!PbEncryptedKeyset} keyset
-   * @return {!Uint8Array}
-   */
-  write(keyset) {}
+export interface KeysetWriter {
+  write(keyset: PbKeyset|PbEncryptedKeyset): Uint8Array;
 }
-
-exports = KeysetWriter;
diff --git a/javascript/primitive_set.js b/javascript/internal/primitive_set.ts
similarity index 61%
rename from javascript/primitive_set.js
rename to javascript/internal/primitive_set.ts
index decfa52..c6b3e4d 100644
--- a/javascript/primitive_set.js
+++ b/javascript/internal/primitive_set.ts
@@ -11,12 +11,11 @@
 // limitations under the License.
 //
 ////////////////////////////////////////////////////////////////////////////////
+import {SecurityException} from '../exception/security_exception';
 
-goog.module('tink.PrimitiveSet');
-
-const CryptoFormat = goog.require('tink.CryptoFormat');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const {PbKeyStatusType, PbKeyset, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
+import {CryptoFormat} from './crypto_format';
+import {PbKeyset, PbKeyStatusType, PbOutputPrefixType} from './proto';
+import {Constructor} from './util';
 
 /**
  * Auxiliary class for PrimitiveSet
@@ -25,50 +24,26 @@
  * @template P
  * @final
  */
-class Entry {
-  /**
-   * @param {!P} primitive
-   * @param {!Uint8Array} identifier
-   * @param {!PbKeyStatusType} keyStatus
-   * @param {!PbOutputPrefixType} outputPrefixType
-   */
-  constructor(primitive, identifier, keyStatus, outputPrefixType) {
-    /** @const @private {!P} */
-    this.primitive_ = primitive;
-    /** @const @private {!Uint8Array} */
-    this.identifier_ = identifier;
-    /** @const @private {!PbKeyStatusType} */
-    this.status_ = keyStatus;
-    /** @const @private {!PbOutputPrefixType} */
-    this.outputPrefixType_ = outputPrefixType;
+export class Entry<P> {
+  constructor(
+      private readonly primitive: P, private readonly identifier: Uint8Array,
+      private readonly keyStatus: PbKeyStatusType,
+      private readonly outputPrefixType: PbOutputPrefixType) {}
+
+  getPrimitive(): P {
+    return this.primitive;
   }
 
-  /**
-   * @return {!P}
-   */
-  getPrimitive() {
-    return this.primitive_;
+  getIdentifier(): Uint8Array {
+    return this.identifier;
   }
 
-  /**
-   * @return {!Uint8Array}
-   */
-  getIdentifier() {
-    return this.identifier_;
+  getKeyStatus(): PbKeyStatusType {
+    return this.keyStatus;
   }
 
-  /**
-   * @return {!PbKeyStatusType}
-   */
-  getKeyStatus() {
-    return this.status_;
-  }
-
-  /**
-   * @return {!PbOutputPrefixType}
-   */
-  getOutputPrefixType() {
-    return this.outputPrefixType_;
+  getOutputPrefixType(): PbOutputPrefixType {
+    return this.outputPrefixType;
   }
 }
 
@@ -90,83 +65,62 @@
  * PrimitiveSet is a public class to allow its use in implementations of custom
  * primitives.
  *
- * @template P
  * @final
  */
-class PrimitiveSet {
-  /**
-   * @param {!Object} primitiveType
-   */
-  constructor(primitiveType) {
-    /**
-     * @private {!Object}
-     */
-    this.primitiveType_ = primitiveType;
-    /**
-     * @private {?Entry<P>}
-     */
-    this.primary_ = null;
-    // Keys have to be stored as strings as two Uint8Arrays holding the same
-    // digits are still different objects.
-    /**
-     * @private {!Map<string, !Array<!Entry<P>>>}
-     */
+export class PrimitiveSet<P> {
+  private primary_: Entry<P>|null = null;
+
+  // Keys have to be stored as strings as two Uint8Arrays holding the same
+  // digits are still different objects.
+  private readonly identifierToPrimitivesMap_: Map<string, Array<Entry<P>>>;
+
+  constructor(private readonly primitiveType: Constructor<P>) {
     this.identifierToPrimitivesMap_ = new Map();
   }
 
   /**
    * Returns the type of primitives contained in this set.
    *
-   * @return {!Object}
    */
-  getPrimitiveType() {
-    return this.primitiveType_;
+  getPrimitiveType(): Constructor<P> {
+    return this.primitiveType;
   }
 
   /**
    * Creates an entry in the primitive table and returns it.
    *
-   * @param {!P} primitive
-   * @param {!PbKeyset.Key} key
    *
-   * @return {!Entry<P>}
    */
-  addPrimitive(primitive, key) {
+  addPrimitive(primitive: P, key: PbKeyset.Key): Entry<P> {
     if (!primitive) {
       throw new SecurityException('Primitive has to be non null.');
     }
     if (!key) {
       throw new SecurityException('Key has to be non null.');
     }
-
     const identifier = CryptoFormat.getOutputPrefix(key);
-    const entry = new Entry(primitive, identifier, key.getStatus(),
-        key.getOutputPrefixType());
-
+    const entry = new Entry(
+        primitive, identifier, key.getStatus(), key.getOutputPrefixType());
     this.addPrimitiveToMap_(entry);
-
     return entry;
   }
 
   /**
    * Returns the entry with the primary primitive.
    *
-   * @return {?Entry<P>}
    */
-  getPrimary() {
+  getPrimary(): Entry<P>|null {
     return this.primary_;
   }
 
   /**
    * Sets given Entry as the primary one.
    *
-   * @param {!Entry<P>} primitive
    */
-  setPrimary(primitive) {
+  setPrimary(primitive: Entry<P>) {
     if (!primitive) {
       throw new SecurityException('Primary cannot be set to null.');
     }
-
     if (primitive.getKeyStatus() != PbKeyStatusType.ENABLED) {
       throw new SecurityException('Primary has to be enabled.');
     }
@@ -186,29 +140,24 @@
           'Primary cannot be set to an entry which is ' +
           'not held by this primitive set.');
     }
-
     this.primary_ = primitive;
   }
 
   /**
    * Returns all primitives using RAW prefix.
    *
-   * @return {!Array<!Entry<P>>}
    */
-  getRawPrimitives() {
+  getRawPrimitives(): Array<Entry<P>> {
     return this.getPrimitives(CryptoFormat.RAW_PREFIX);
   }
 
   /**
    * Returns the entries with primitive identified with identifier.
    *
-   * @param {!Uint8Array} identifier
    *
-   * @return {!Array<!Entry<P>>}
    */
-  getPrimitives(identifier) {
+  getPrimitives(identifier: Uint8Array): Array<Entry<P>> {
     const result = this.getPrimitivesFromMap_(identifier);
-
     if (!result) {
       return [];
     } else {
@@ -219,12 +168,10 @@
   /**
    * Returns a set of primitives which corresponds to the given identifier.
    *
-   * @private
-   * @param {!Uint8Array|string} identifier
    *
-   * @return {!Array<!Entry<P>>|undefined}
    */
-  getPrimitivesFromMap_(identifier) {
+  private getPrimitivesFromMap_(identifier: Uint8Array|
+                                string): Array<Entry<P>>|undefined {
     if (identifier instanceof Uint8Array) {
       identifier = [...identifier].toString();
     }
@@ -234,15 +181,11 @@
   /**
    * Add primitive to map.
    *
-   * @private
-   * @param {!Entry<P>} entry
    */
-  addPrimitiveToMap_(entry) {
+  private addPrimitiveToMap_(entry: Entry<P>) {
     const identifier = entry.getIdentifier();
     const id = [...identifier].toString();
-
-    let existing = this.getPrimitivesFromMap_(id);
-
+    const existing = this.getPrimitivesFromMap_(id);
     if (!existing) {
       this.identifierToPrimitivesMap_.set(id, [entry]);
     } else {
@@ -251,8 +194,3 @@
     }
   }
 }
-
-exports = {
-  Entry,
-  PrimitiveSet,
-};
diff --git a/javascript/primitive_set_test.js b/javascript/internal/primitive_set_test.js
similarity index 95%
rename from javascript/primitive_set_test.js
rename to javascript/internal/primitive_set_test.js
index cf2b9e6..cd3a4be 100644
--- a/javascript/primitive_set_test.js
+++ b/javascript/internal/primitive_set_test.js
@@ -15,11 +15,11 @@
 goog.module('tink.PrimitiveSetTest');
 goog.setTestOnly('tink.PrimitiveSetTest');
 
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
 const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
-const CryptoFormat = goog.require('tink.CryptoFormat');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
+const {CryptoFormat} = goog.require('google3.third_party.tink.javascript.internal.crypto_format');
 const {PbKeyStatusType, PbKeysetKey, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
+const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 
 describe('primitive set test', function() {
   /////////////////////////////////////////////////////////////////////////////
@@ -39,6 +39,20 @@
     fail('An exception should be thrown.');
   });
 
+  it('add primitive null primitive', function() {
+    const primitive = null;
+    const key = createKey();
+    const primitiveSet = new PrimitiveSet.PrimitiveSet(Aead);
+
+    try {
+      primitiveSet.addPrimitive(primitive, key);
+    } catch (e) {
+      expect(e.toString()).toBe(ExceptionText.addingNullPrimitive());
+      return;
+    }
+    fail('An exception should be thrown.');
+  });
+
   it('add primitive multiple times should work', function() {
     const key = createKey();
     const primitiveSet = new PrimitiveSet.PrimitiveSet(Aead);
diff --git a/javascript/primitive_wrapper.js b/javascript/internal/primitive_wrapper.ts
similarity index 77%
rename from javascript/primitive_wrapper.js
rename to javascript/internal/primitive_wrapper.ts
index 51e9ac8..bbc9d2d 100644
--- a/javascript/primitive_wrapper.js
+++ b/javascript/internal/primitive_wrapper.ts
@@ -11,10 +11,8 @@
 // limitations under the License.
 //
 ////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.PrimitiveWrapper');
-
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
+import * as PrimitiveSet from './primitive_set';
+import {Constructor} from './util';
 
 /**
  * Basic interface for wrapping a primitive.
@@ -23,25 +21,17 @@
  * cryptographic task. This is done by the PrimitiveWrapper. Whenever a new
  * primitive type is added to Tink, the user should define a new
  * PrimitiveWrapper and register it with the Registry.
- *
- * @template P
- * @record
  */
-class PrimitiveWrapper {
+export interface PrimitiveWrapper<P> {
   /**
    * Wraps a PrimitiveSet and returns a single instance.
    *
-   * @param {!PrimitiveSet.PrimitiveSet<P>} primitiveSet
-   * @return {!P}
    */
-  wrap(primitiveSet) {}
+  wrap(primitiveSet: PrimitiveSet.PrimitiveSet<P>): P;
 
   /**
    * Returns the type of the managed primitive. Used for internal management.
    *
-   * @return {!Object}
    */
-  getPrimitiveType() {}
+  getPrimitiveType(): Constructor<P>;
 }
-
-exports = PrimitiveWrapper;
diff --git a/javascript/proto_test.js b/javascript/internal/proto_test.js
similarity index 100%
rename from javascript/proto_test.js
rename to javascript/internal/proto_test.js
diff --git a/javascript/internal/registry.ts b/javascript/internal/registry.ts
new file mode 100644
index 0000000..0cad8b6
--- /dev/null
+++ b/javascript/internal/registry.ts
@@ -0,0 +1,244 @@
+// 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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @fileoverview Registry for KeyManagers.
+ *
+ * Registry maps supported key types to corresponding KeyManager objects (i.e.
+ * the KeyManagers which may instantiate the primitive corresponding to the
+ * given key or generate new key of the given type). Keeping KeyManagers for all
+ * primitives in a single Registry (rather than having a separate keyManager per
+ * primitive) enables modular construction of compound primitives from "simple"
+ * ones (e.g. AES-CTR-HMAC AEAD encryption from IND-CPA encryption and MAC).
+ *
+ * Regular users will not usually work with Registry directly, but via primitive
+ * factories, which query Registry for the specific KeyManagers in the
+ * background.
+ */
+
+import {SecurityException} from '../exception/security_exception';
+
+import * as KeyManager from './key_manager';
+import * as PrimitiveSet from './primitive_set';
+import {PrimitiveWrapper} from './primitive_wrapper';
+import {PbKeyData, PbKeyTemplate, PbMessage} from './proto';
+
+// key managers maps
+const typeToManagerMap_:
+    Map<string, KeyManager.KeyManager<AnyDuringMigration>> = new Map();
+
+const typeToNewKeyAllowedMap_: Map<string, boolean> = new Map();
+
+// primitive wrappers map
+const primitiveTypeToWrapper_:
+    Map<AnyDuringMigration, PrimitiveWrapper<AnyDuringMigration>> = new Map();
+
+/**
+ * Register the given manager for the given key type. Manager must be
+ * non-nullptr. New keys are allowed if not specified.
+ */
+export function registerKeyManager<P>(
+    manager: KeyManager.KeyManager<P>, opt_newKeyAllowed?: boolean) {
+  if (opt_newKeyAllowed === undefined) {
+    opt_newKeyAllowed = true;
+  }
+  if (!manager) {
+    throw new SecurityException('Key manager cannot be null.');
+  }
+  const typeUrl = manager.getKeyType();
+  if (typeToManagerMap_.has(typeUrl)) {
+    // Cannot overwrite the existing key manager by a new one.
+    if (!(typeToManagerMap_.get(typeUrl) instanceof manager.constructor)) {
+      throw new SecurityException(
+          'Key manager for key type ' + typeUrl +
+          ' has already been registered and cannot be overwritten.');
+    }
+
+    // It is forbidden to change new_key_allowed from false to true.
+    if (!typeToNewKeyAllowedMap_.get(typeUrl) && opt_newKeyAllowed) {
+      throw new SecurityException(
+          'Key manager for key type ' + typeUrl +
+          ' has already been registered with forbidden new key operation.');
+    }
+    typeToNewKeyAllowedMap_.set(typeUrl, opt_newKeyAllowed);
+  }
+  typeToManagerMap_.set(typeUrl, manager);
+  typeToNewKeyAllowedMap_.set(typeUrl, opt_newKeyAllowed);
+}
+
+/**
+ * Returns a key manager for the given key type or throws an exception if no
+ * such manager found.
+ *
+ * @param typeUrl -- key type
+ *
+ */
+export function getKeyManager<P>(typeUrl: string): KeyManager.KeyManager<P> {
+  const res = typeToManagerMap_.get(typeUrl);
+  if (!res) {
+    throw new SecurityException(
+        'Key manager for key type ' + typeUrl + ' has not been registered.');
+  }
+  return res;
+}
+
+/**
+ * It finds KeyManager according to key type (which is either given by
+ * PbKeyData or given by opt_typeUrl), than calls the corresponding
+ * manager's getPrimitive method.
+ *
+ * Either key is of type PbKeyData or opt_typeUrl must be provided.
+ *
+ * @param key -- key is either a proto of some key
+ *     or key data.
+ * @param opt_typeUrl -- key type
+ * @this {typeof Registry}
+ *
+ */
+export async function getPrimitive<P>(
+    primitiveType: AnyDuringMigration, key: PbKeyData|PbMessage,
+    opt_typeUrl?: string|null): Promise<P> {
+  if (key instanceof PbKeyData) {
+    if (opt_typeUrl && key.getTypeUrl() != opt_typeUrl) {
+      throw new SecurityException(
+          'Key type is ' + opt_typeUrl + ', but it is expected to be ' +
+          key.getTypeUrl() + ' or undefined.');
+    }
+    opt_typeUrl = key.getTypeUrl();
+  }
+  if (!opt_typeUrl) {
+    throw new SecurityException('Key type has to be specified.');
+  }
+  const manager = getKeyManager<P>(opt_typeUrl);
+  return manager.getPrimitive(primitiveType, key);
+}
+
+/**
+ * Generates a new PbKeyData for the specified keyTemplate. It finds a
+ * KeyManager given by keyTemplate.typeUrl and calls the newKeyData method of
+ * that manager.
+ *
+ *
+ *
+ */
+export async function newKeyData(keyTemplate: PbKeyTemplate):
+    Promise<PbKeyData> {
+  const manager = getKeyManagerWithNewKeyAllowedCheck_(keyTemplate);
+  return manager.getKeyFactory().newKeyData(keyTemplate.getValue_asU8());
+}
+
+/**
+ * Generates a new key for the specified keyTemplate using the
+ * KeyManager determined by typeUrl field of the keyTemplate.
+ *
+ *
+ *
+ * @return returns a key proto
+ */
+export async function newKey(keyTemplate: PbKeyTemplate): Promise<PbMessage> {
+  const manager = getKeyManagerWithNewKeyAllowedCheck_(keyTemplate);
+  return manager.getKeyFactory().newKey(keyTemplate.getValue_asU8());
+}
+
+/**
+ * Convenience method for extracting the public key data from the private key
+ * given by serializedPrivateKey.
+ * It looks up a KeyManager identified by typeUrl, which must hold
+ * PrivateKeyFactory, and calls getPublicKeyData method of that factory.
+ *
+ */
+export function getPublicKeyData(
+    typeUrl: string, serializedPrivateKey: Uint8Array): PbKeyData {
+  const manager = getKeyManager(typeUrl);
+
+  // This solution might cause some problems in the future due to Closure
+  // compiler optimizations, which may map factory.getPublicKeyData to
+  // concrete function.
+  const factory = (manager.getKeyFactory() as AnyDuringMigration);
+  if (!factory.getPublicKeyData) {
+    throw new SecurityException(
+        'Key manager for key type ' + typeUrl +
+        ' does not have a private key factory.');
+  }
+  return factory.getPublicKeyData(serializedPrivateKey);
+}
+
+/**
+ * Resets the registry.
+ * After reset the registry is empty, i.e. it contains no key managers.
+ *
+ * This method is only for testing.
+ */
+export function reset() {
+  typeToManagerMap_.clear();
+  typeToNewKeyAllowedMap_.clear();
+  primitiveTypeToWrapper_.clear();
+}
+
+/**
+ * It finds a KeyManager given by keyTemplate.typeUrl and returns it if it
+ * allows creating new keys.
+ *
+ *
+ */
+function getKeyManagerWithNewKeyAllowedCheck_(keyTemplate: PbKeyTemplate):
+    KeyManager.KeyManager<AnyDuringMigration> {
+  const keyType = keyTemplate.getTypeUrl();
+  const manager = getKeyManager(keyType);
+  if (!typeToNewKeyAllowedMap_.get(keyType)) {
+    throw new SecurityException(
+        'New key operation is forbidden for ' +
+        'key type: ' + keyType + '.');
+  }
+  return manager;
+}
+
+/**
+ * Tries to register a primitive wrapper.
+ */
+export function registerPrimitiveWrapper<P>(wrapper: PrimitiveWrapper<P>) {
+  if (!wrapper) {
+    throw new SecurityException('primitive wrapper cannot be null');
+  }
+  const primitiveType = wrapper.getPrimitiveType();
+  if (!primitiveType) {
+    throw new SecurityException('primitive wrapper cannot be undefined');
+  }
+  if (primitiveTypeToWrapper_.has(primitiveType)) {
+    // Cannot overwrite the existing key manager by a new one.
+    if (!(primitiveTypeToWrapper_.get(primitiveType) instanceof
+          wrapper.constructor)) {
+      throw new SecurityException(
+          'primitive wrapper for type ' + primitiveType +
+          ' has already been registered and cannot be overwritten');
+    }
+  }
+  primitiveTypeToWrapper_.set(primitiveType, wrapper);
+}
+
+/**
+ * Wraps a PrimitiveSet and returns a single instance.
+ */
+export function wrap<P>(primitiveSet: PrimitiveSet.PrimitiveSet<P>): P {
+  if (!primitiveSet) {
+    throw new SecurityException('primitive set cannot be null.');
+  }
+  const primitiveType = primitiveSet.getPrimitiveType();
+  const wrapper = primitiveTypeToWrapper_.get(primitiveType);
+  if (!wrapper) {
+    throw new SecurityException(
+        'no primitive wrapper found for type ' + primitiveType);
+  }
+  return wrapper.wrap(primitiveSet);
+}
diff --git a/javascript/registry_test.js b/javascript/internal/registry_test.js
similarity index 98%
rename from javascript/registry_test.js
rename to javascript/internal/registry_test.js
index 6868bee..a7bf595 100644
--- a/javascript/registry_test.js
+++ b/javascript/internal/registry_test.js
@@ -19,14 +19,14 @@
 const AeadConfig = goog.require('tink.aead.AeadConfig');
 const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
 const AesCtrHmacAeadKeyManager = goog.require('tink.aead.AesCtrHmacAeadKeyManager');
-const EncryptThenAuthenticate = goog.require('tink.subtle.EncryptThenAuthenticate');
+const {EncryptThenAuthenticate} = goog.require('google3.third_party.tink.javascript.subtle.encrypt_then_authenticate');
 const HybridConfig = goog.require('tink.hybrid.HybridConfig');
 const HybridKeyTemplates = goog.require('tink.hybrid.HybridKeyTemplates');
-const KeyManager = goog.require('tink.KeyManager');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
 const {Mac} = goog.require('google3.third_party.tink.javascript.mac.internal.mac');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const PrimitiveWrapper = goog.require('tink.PrimitiveWrapper');
-const Registry = goog.require('tink.Registry');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
+const {PrimitiveWrapper} = goog.require('google3.third_party.tink.javascript.internal.primitive_wrapper');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
 const {PbAesCtrHmacAeadKey, PbAesCtrHmacAeadKeyFormat, PbAesCtrKey, PbAesCtrKeyFormat, PbAesCtrParams, PbEciesAeadHkdfPrivateKey, PbEciesAeadHkdfPublicKey, PbHashType, PbHmacKeyFormat, PbHmacParams, PbKeyData, PbKeyTemplate, PbMessage} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
diff --git a/javascript/util.js b/javascript/internal/util.ts
similarity index 68%
rename from javascript/util.js
rename to javascript/internal/util.ts
index 0f0bf0d..f90df1e 100644
--- a/javascript/util.js
+++ b/javascript/internal/util.ts
@@ -11,48 +11,51 @@
 // limitations under the License.
 //
 ////////////////////////////////////////////////////////////////////////////////
+import {SecurityException} from '../exception/security_exception';
+import * as Bytes from '../subtle/bytes';
+import * as EllipticCurves from '../subtle/elliptic_curves';
 
-goog.module('tink.Util');
+import {PbEllipticCurveType, PbHashType, PbKeyset, PbKeyStatusType, PbOutputPrefixType, PbPointFormat} from './proto';
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const {PbEllipticCurveType, PbHashType, PbKeyStatusType, PbKeyset, PbOutputPrefixType, PbPointFormat} = goog.require('google3.third_party.tink.javascript.internal.proto');
+/**
+ * A type representing the constructor function for a given class. Unlike
+ * TypeScript's built-in `new` types, this works with abstract classes. It is
+ * used to describe the relationship between a primitive type object and its
+ * instances.
+ */
+export type Constructor<T> = Function&{prototype: T};
 
 /**
  * Validates the given key and throws SecurityException if it is invalid.
  *
- * @param {!PbKeyset.Key} key
  */
-const validateKey = function(key) {
+export function validateKey(key: PbKeyset.Key) {
   if (!key) {
     throw new SecurityException('Key should be non null.');
   }
   if (!key.getKeyData()) {
-    throw new SecurityException('Key data are missing for key '
-        + key.getKeyId() + '.');
+    throw new SecurityException(
+        'Key data are missing for key ' + key.getKeyId() + '.');
   }
   if (key.getOutputPrefixType() === PbOutputPrefixType.UNKNOWN_PREFIX) {
-    throw new SecurityException('Key ' + key.getKeyId() +
-        ' has unknown output prefix type.');
+    throw new SecurityException(
+        'Key ' + key.getKeyId() + ' has unknown output prefix type.');
   }
   if (key.getStatus() === PbKeyStatusType.UNKNOWN_STATUS) {
-    throw new SecurityException('Key ' + key.getKeyId() +
-        ' has unknown status.');
+    throw new SecurityException(
+        'Key ' + key.getKeyId() + ' has unknown status.');
   }
-};
+}
 
 /**
  * Validates the given keyset and throws SecurityException if it is invalid.
  *
- * @param {!PbKeyset} keyset
  */
-const validateKeyset = function(keyset) {
+export function validateKeyset(keyset: PbKeyset) {
   if (!keyset || !keyset.getKeyList() || keyset.getKeyList().length < 1) {
     throw new SecurityException(
         'Keyset should be non null and must contain at least one key.');
   }
-
   let hasPrimary = false;
   const numberOfKeys = keyset.getKeyList().length;
   for (let i = 0; i < numberOfKeys; i++) {
@@ -66,12 +69,12 @@
       hasPrimary = true;
     }
   }
-
   if (!hasPrimary) {
-    throw new SecurityException('Primary key has to be in the keyset and ' +
+    throw new SecurityException(
+        'Primary key has to be in the keyset and ' +
         'has to be enabled.');
   }
-};
+}
 
 // Functions which are useful for implementation of
 // private and public EC keys.
@@ -83,11 +86,9 @@
  * keyValue values in proto might either have some leading zeros or the leading
  * zeros might be missing.
  *
- * @param {!Uint8Array} bigEndianNumber
- * @param {number} sizeInBytes
- * @return {!Uint8Array}
  */
-const bigEndianNumberToCorrectLength = function(bigEndianNumber, sizeInBytes) {
+export function bigEndianNumberToCorrectLength(
+    bigEndianNumber: Uint8Array, sizeInBytes: number): Uint8Array {
   const numberLen = bigEndianNumber.length;
   if (numberLen < sizeInBytes) {
     const zeros = new Uint8Array(sizeInBytes - numberLen);
@@ -103,13 +104,10 @@
     return bigEndianNumber.slice(numberLen - sizeInBytes, numberLen);
   }
   return bigEndianNumber;
-};
+}
 
-/**
- * @param {!PbEllipticCurveType} curveTypeProto
- * @return {!EllipticCurves.CurveType}
- */
-const curveTypeProtoToSubtle = function(curveTypeProto) {
+export function curveTypeProtoToSubtle(curveTypeProto: PbEllipticCurveType):
+    EllipticCurves.CurveType {
   switch (curveTypeProto) {
     case PbEllipticCurveType.NIST_P256:
       return EllipticCurves.CurveType.P256;
@@ -120,13 +118,9 @@
     default:
       throw new SecurityException('Unknown curve type.');
   }
-};
+}
 
-/**
- * @param {!PbHashType} hashTypeProto
- * @return {string}
- */
-const hashTypeProtoToString = function(hashTypeProto) {
+export function hashTypeProtoToString(hashTypeProto: PbHashType): string {
   switch (hashTypeProto) {
     case PbHashType.SHA1:
       return 'SHA-1';
@@ -137,13 +131,10 @@
     default:
       throw new SecurityException('Unknown hash type.');
   }
-};
+}
 
-/**
- * @param {!PbPointFormat} pointFormatProto
- * @return {!EllipticCurves.PointFormatType}
- */
-const pointFormatProtoToSubtle = function(pointFormatProto) {
+export function pointFormatProtoToSubtle(pointFormatProto: PbPointFormat):
+    EllipticCurves.PointFormatType {
   switch (pointFormatProto) {
     case PbPointFormat.UNCOMPRESSED:
       return EllipticCurves.PointFormatType.UNCOMPRESSED;
@@ -154,13 +145,4 @@
     default:
       throw new SecurityException('Unknown point format.');
   }
-};
-
-exports = {
-  bigEndianNumberToCorrectLength,
-  curveTypeProtoToSubtle,
-  hashTypeProtoToString,
-  pointFormatProtoToSubtle,
-  validateKey,
-  validateKeyset,
-};
+}
diff --git a/javascript/util_test.js b/javascript/internal/util_test.js
similarity index 97%
rename from javascript/util_test.js
rename to javascript/internal/util_test.js
index 41cc01c..5ba4984 100644
--- a/javascript/util_test.js
+++ b/javascript/internal/util_test.js
@@ -15,8 +15,8 @@
 goog.module('tink.UtilTest');
 goog.setTestOnly('tink.UtilTest');
 
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Util = goog.require('tink.Util');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbEllipticCurveType, PbHashType, PbKeyData, PbKeyStatusType, PbKeyset, PbOutputPrefixType, PbPointFormat} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/javascript/keyset_handle.ts b/javascript/keyset_handle.ts
index f88a41c..ca2322f 100644
--- a/javascript/keyset_handle.ts
+++ b/javascript/keyset_handle.ts
@@ -1 +1 @@
-export {default as KeysetHandle} from 'goog:tink.KeysetHandle';  // from //third_party/tink/javascript:keyset_handle_legacy
+export {generateNew, KeysetHandle} from './internal/keyset_handle';
diff --git a/javascript/keyset_handle_legacy.js b/javascript/keyset_handle_legacy.js
deleted file mode 100644
index 9349790..0000000
--- a/javascript/keyset_handle_legacy.js
+++ /dev/null
@@ -1,229 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.KeysetHandle');
-
-const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
-const {InvalidArgumentsException} = goog.require('google3.third_party.tink.javascript.exception.invalid_arguments_exception');
-const KeyManager = goog.require('tink.KeyManager');
-const KeysetReader = goog.require('tink.KeysetReader');
-const KeysetWriter = goog.require('tink.KeysetWriter');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Util = goog.require('tink.Util');
-const {PbKeyMaterialType, PbKeyStatusType, PbKeyTemplate, PbKeyset} = goog.require('google3.third_party.tink.javascript.internal.proto');
-
-/**
- * Keyset handle provide abstracted access to Keysets, to limit the exposure of
- * actual protocol buffers that hold sensitive key material.
- *
- * @final
- */
-class KeysetHandle {
-  /**
-   * @param {!PbKeyset} keyset
-   */
-  constructor(keyset) {
-    Util.validateKeyset(keyset);
-
-    /** @const @private {!PbKeyset} */
-    this.keyset_ = keyset;
-  }
-
-  /**
-   * Creates a KeysetHandle from an encrypted keyset obtained via reader, using
-   * masterKeyAead to decrypt the keyset.
-   *
-   * @param {!KeysetReader} reader
-   * @param {!Aead} masterKeyAead
-   *
-   * @return {!Promise<!KeysetHandle>}
-   */
-  static async read(reader, masterKeyAead) {
-    // TODO implement
-    throw new SecurityException('KeysetHandle -- read: Not implemented yet.');
-  }
-
-  /**
-   * Creates a KeysetHandle from a keyset, obtained via reader, which
-   * must contain no secret key material.
-   *
-   * This can be used to load public keysets or envelope encryption keysets.
-   * Users that need to load cleartext keysets can use CleartextKeysetHandle.
-   *
-   * @param {!KeysetReader} reader
-   * @return {!KeysetHandle}
-   */
-  static readNoSecret(reader) {
-    if (reader === null) {
-      throw new SecurityException('Reader has to be non-null.');
-    }
-    const keyset = reader.read();
-    const keyList = keyset.getKeyList();
-    for (let key of keyList) {
-      switch (key.getKeyData().getKeyMaterialType()) {
-        case PbKeyMaterialType.ASYMMETRIC_PUBLIC:  // fall through
-        case PbKeyMaterialType.REMOTE:
-          continue;
-      }
-      throw new SecurityException('Keyset contains secret key material.');
-    }
-    return new KeysetHandle(keyset);
-  }
-
-  /**
-   * Returns a new KeysetHandle that contains a single new key generated
-   * according to keyTemplate.
-   *
-   * @param {!PbKeyTemplate} keyTemplate
-   *
-   * @return {!Promise<!KeysetHandle>}
-   */
-  static async generateNew(keyTemplate) {
-    // TODO(thaidn): move this to a key manager.
-    const keyset = await KeysetHandle.generateNewKeyset_(keyTemplate);
-    return new KeysetHandle(keyset);
-  }
-
-  /**
-   * Generates a new Keyset that contains a single new key generated
-   * according to keyTemplate.
-   *
-   * @param {!PbKeyTemplate} keyTemplate
-   * @private
-   * @return {!Promise<!PbKeyset>}
-   */
-  static async generateNewKeyset_(keyTemplate) {
-    const key = new PbKeyset.Key()
-                    .setStatus(PbKeyStatusType.ENABLED)
-                    .setOutputPrefixType(keyTemplate.getOutputPrefixType());
-    const keyId = KeysetHandle.generateNewKeyId_();
-    key.setKeyId(keyId);
-    const keyData = await Registry.newKeyData(keyTemplate);
-    key.setKeyData(keyData);
-    const keyset = new PbKeyset();
-    keyset.addKey(key);
-    keyset.setPrimaryKeyId(keyId);
-    return keyset;
-  }
-
-  /**
-   * Generates a new random key ID.
-   *
-   * @private
-   * @return {number} The key ID.
-   */
-  static generateNewKeyId_() {
-    const bytes = Random.randBytes(4);
-    let value = 0;
-    for (let i = 0; i < bytes.length; i++) {
-      value += (bytes[i] & 0xFF) << (i * 8);
-    }
-    // Make sure the key ID is a positive integer smaller than 2^32.
-    return Math.abs(value) % 2 ** 32;
-  };
-
-
-  /**
-   * Returns a primitive that uses key material from this keyset handle. If
-   * opt_customKeyManager is defined then the provided key manager is used to
-   * instantiate primitives. Otherwise key manager from Registry is used.
-   *
-   * @template P
-   *
-   * @param {!Object} primitiveType
-   * @param {?KeyManager.KeyManager<P>=} opt_customKeyManager
-   *
-   * @return {!Promise<!P>}
-   */
-  async getPrimitive(primitiveType, opt_customKeyManager) {
-    if (!primitiveType) {
-      throw new InvalidArgumentsException('primitive type must be non-null');
-    }
-    const primitiveSet =
-        await this.getPrimitiveSet(primitiveType, opt_customKeyManager);
-    return Registry.wrap(primitiveSet);
-  }
-
-  /**
-   * Creates a set of primitives corresponding to the keys with status Enabled
-   * in the given keysetHandle, assuming all the correspoding key managers are
-   * present (keys with status different from Enabled are skipped). If provided
-   * uses customKeyManager instead of registered key managers for keys supported
-   * by the customKeyManager.
-   *
-   * @template P
-   * @package Visible for testing.
-   *
-   * @param {!Object} primitiveType
-   * @param {?KeyManager.KeyManager<P>=} opt_customKeyManager
-   *
-   * @return {!Promise.<!PrimitiveSet.PrimitiveSet<P>>}
-   */
-  async getPrimitiveSet(primitiveType, opt_customKeyManager) {
-    const primitiveSet = new PrimitiveSet.PrimitiveSet(primitiveType);
-    const keys = this.keyset_.getKeyList();
-    const keysLength = keys.length;
-    for (let i = 0; i < keysLength; i++) {
-      const key = keys[i];
-      if (key.getStatus() === PbKeyStatusType.ENABLED) {
-        const keyData = key.getKeyData();
-        if (!keyData) {
-          throw new SecurityException('Key data has to be non null.');
-        }
-        let primitive;
-        if (opt_customKeyManager &&
-            opt_customKeyManager.getKeyType() === keyData.getTypeUrl()) {
-          primitive =
-              await opt_customKeyManager.getPrimitive(primitiveType, keyData);
-        } else {
-          primitive = await Registry.getPrimitive(primitiveType, keyData);
-        }
-        const entry = primitiveSet.addPrimitive(primitive, key);
-        if (key.getKeyId() === this.keyset_.getPrimaryKeyId()) {
-          primitiveSet.setPrimary(entry);
-        }
-      }
-    }
-    return primitiveSet;
-  }
-
-
-  /**
-   * Encrypts the underlying keyset with the provided masterKeyAead wnd writes
-   * the resulting encryptedKeyset to the given writer which must be non-null.
-   *
-   * @param {!KeysetWriter} writer
-   * @param {!Aead} masterKeyAead
-   *
-   */
-  async write(writer, masterKeyAead) {
-    // TODO implement
-    throw new SecurityException('KeysetHandle -- write: Not implemented yet.');
-  }
-
-  /**
-   * Returns the keyset held by this KeysetHandle.
-   *
-   * @package
-   * @return {!PbKeyset}
-   */
-  getKeyset() {
-    return this.keyset_;
-  }
-}
-
-exports = KeysetHandle;
diff --git a/javascript/mac/subtle/hmac.ts b/javascript/mac/subtle/hmac.ts
index fc863e4..5b7d1b5 100644
--- a/javascript/mac/subtle/hmac.ts
+++ b/javascript/mac/subtle/hmac.ts
@@ -1 +1 @@
-export {default as Hmac} from 'goog:tink.subtle.Hmac';  // from //third_party/tink/javascript/subtle:mac
+export {fromRawKey, Hmac} from '../../subtle/hmac';
diff --git a/javascript/mac/subtle/index.ts b/javascript/mac/subtle/index.ts
index 261f749..39f4d62 100644
--- a/javascript/mac/subtle/index.ts
+++ b/javascript/mac/subtle/index.ts
@@ -1 +1 @@
-export * from './hmac';
+export {fromRawKey as hmacFromRawKey, Hmac} from './hmac';
diff --git a/javascript/registry.js b/javascript/registry.js
deleted file mode 100644
index 6a8720c..0000000
--- a/javascript/registry.js
+++ /dev/null
@@ -1,297 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.Registry');
-
-const KeyManager = goog.require('tink.KeyManager');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const PrimitiveWrapper = goog.require('tink.PrimitiveWrapper');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const {PbKeyData, PbKeyTemplate, PbMessage} = goog.require('google3.third_party.tink.javascript.internal.proto');
-
-/**
- * Registry for KeyManagers.
- *
- * Registry maps supported key types to corresponding KeyManager objects (i.e.
- * the KeyManagers which may instantiate the primitive corresponding to the
- * given key or generate new key of the given type). Keeping KeyManagers for all
- * primitives in a single Registry (rather than having a separate keyManager per
- * primitive) enables modular construction of compound primitives from "simple"
- * ones (e.g. AES-CTR-HMAC AEAD encryption from IND-CPA encryption and MAC).
- *
- * Regular users will not usually work with Registry directly, but via primitive
- * factories, which query Registry for the specific KeyManagers in the
- * background.
- *
- * @final
- */
-class Registry {
-  /**
-   * Register the given manager for the given key type. Manager must be
-   * non-nullptr. New keys are allowed if not specified.
-   *
-   * @template P
-   * @static
-   *
-   * @param {!KeyManager.KeyManager<P>} manager
-   * @param {boolean=} opt_newKeyAllowed
-   */
-  static registerKeyManager(manager, opt_newKeyAllowed) {
-    if (opt_newKeyAllowed === undefined) {
-      opt_newKeyAllowed = true;
-    }
-    if (!manager) {
-      throw new SecurityException('Key manager cannot be null.');
-    }
-    const typeUrl = manager.getKeyType();
-
-    if (Registry.typeToManagerMap_.has(typeUrl)) {
-      // Cannot overwrite the existing key manager by a new one.
-      if (!(Registry.typeToManagerMap_.get(typeUrl) instanceof
-            manager.constructor)) {
-        throw new SecurityException(
-            'Key manager for key type ' + typeUrl +
-            ' has already been registered and cannot be overwritten.');
-      }
-
-      // It is forbidden to change new_key_allowed from false to true.
-      if (!(Registry.typeToNewKeyAllowedMap_.get(typeUrl)) &&
-          opt_newKeyAllowed) {
-        throw new SecurityException(
-            'Key manager for key type ' + typeUrl +
-            ' has already been registered with forbidden new key operation.');
-      }
-      Registry.typeToNewKeyAllowedMap_.set(typeUrl, opt_newKeyAllowed);
-    }
-
-    Registry.typeToManagerMap_.set(typeUrl, manager);
-    Registry.typeToNewKeyAllowedMap_.set(typeUrl, opt_newKeyAllowed);
-  }
-
-  /**
-   * Returns a key manager for the given key type or throws an exception if no
-   * such manager found.
-   *
-   * @template P
-   * @static
-   *
-   * @param {string} typeUrl -- key type
-   *
-   * @return {!KeyManager.KeyManager<P>}
-   */
-  static getKeyManager(typeUrl) {
-    const res = Registry.typeToManagerMap_.get(typeUrl);
-    if (!res) {
-      throw new SecurityException(
-          'Key manager for key type ' + typeUrl + ' has not been registered.');
-    }
-    return res;
-  }
-
-  /**
-   * It finds KeyManager according to key type (which is either given by
-   * PbKeyData or given by opt_typeUrl), than calls the corresponding
-   * manager's getPrimitive method.
-   *
-   * Either key is of type PbKeyData or opt_typeUrl must be provided.
-   *
-   * @template P
-   * @static
-   *
-   * @param {!Object} primitiveType
-   * @param {!PbKeyData|!PbMessage} key -- key is either a proto of some key
-   *     or key data.
-   * @param {?string=} opt_typeUrl -- key type
-   *
-   * @return {!Promise.<!P>}
-   */
-  static async getPrimitive(primitiveType, key, opt_typeUrl) {
-    if (key instanceof PbKeyData) {
-      if (opt_typeUrl && key.getTypeUrl() != opt_typeUrl) {
-        throw new SecurityException(
-            'Key type is ' + opt_typeUrl + ', but it is expected to be ' +
-            key.getTypeUrl() + ' or undefined.');
-      }
-      opt_typeUrl = key.getTypeUrl();
-    }
-
-    if (!opt_typeUrl) {
-      throw new SecurityException('Key type has to be specified.');
-    }
-
-    const manager = Registry.getKeyManager(opt_typeUrl);
-    return await manager.getPrimitive(primitiveType, key);
-  }
-
-  /**
-   * Generates a new PbKeyData for the specified keyTemplate. It finds a
-   * KeyManager given by keyTemplate.typeUrl and calls the newKeyData method of
-   * that manager.
-   *
-   * @static
-   *
-   * @param {!PbKeyTemplate} keyTemplate
-   *
-   * @return {!Promise<!PbKeyData>}
-   */
-  static async newKeyData(keyTemplate) {
-    const manager = Registry.getKeyManagerWithNewKeyAllowedCheck_(keyTemplate);
-    return await manager.getKeyFactory().newKeyData(
-        keyTemplate.getValue_asU8());
-  }
-
-  /**
-   * Generates a new key for the specified keyTemplate using the
-   * KeyManager determined by typeUrl field of the keyTemplate.
-   *
-   * @static
-   *
-   * @param {!PbKeyTemplate} keyTemplate
-   *
-   * @return {!Promise<!PbMessage>} returns a key proto
-   */
-  static async newKey(keyTemplate) {
-    const manager = Registry.getKeyManagerWithNewKeyAllowedCheck_(keyTemplate);
-    return await manager.getKeyFactory().newKey(keyTemplate.getValue_asU8());
-  }
-
-  /**
-   * Convenience method for extracting the public key data from the private key
-   * given by serializedPrivateKey.
-   * It looks up a KeyManager identified by typeUrl, which must hold
-   * PrivateKeyFactory, and calls getPublicKeyData method of that factory.
-   *
-   * @param {string} typeUrl
-   * @param {!Uint8Array} serializedPrivateKey
-   * @return {!PbKeyData}
-   */
-  static getPublicKeyData(typeUrl, serializedPrivateKey) {
-    const manager = Registry.getKeyManager(typeUrl);
-    // This solution might cause some problems in the future due to Closure
-    // compiler optimizations, which may map factory.getPublicKeyData to
-    // concrete function.
-    const factory = /** @type{?} */ (manager.getKeyFactory());
-    if (!factory.getPublicKeyData) {
-      throw new SecurityException(
-          'Key manager for key type ' + typeUrl +
-          ' does not have a private key factory.');
-    }
-    return factory.getPublicKeyData(serializedPrivateKey);
-  }
-
-  /**
-   * Resets the registry.
-   * After reset the registry is empty, i.e. it contains no key managers.
-   *
-   * This method is only for testing.
-   *
-   * @static
-   */
-  static reset() {
-    Registry.typeToManagerMap_.clear();
-    Registry.typeToNewKeyAllowedMap_.clear();
-    Registry.primitiveTypeToWrapper_.clear();
-  }
-
-  /**
-   * It finds a KeyManager given by keyTemplate.typeUrl and returns it if it
-   * allows creating new keys.
-   *
-   * @private
-   * @param {!PbKeyTemplate} keyTemplate
-   *
-   * @return {!KeyManager.KeyManager}
-   */
-  static getKeyManagerWithNewKeyAllowedCheck_(keyTemplate) {
-    const keyType = keyTemplate.getTypeUrl();
-    const manager = Registry.getKeyManager(keyType);
-    if (!Registry.typeToNewKeyAllowedMap_.get(keyType)) {
-      throw new SecurityException(
-          'New key operation is forbidden for ' +
-          'key type: ' + keyType + '.');
-    }
-
-    return manager;
-  }
-
-  /**
-   * Tries to register a primitive wrapper.
-   *
-   * @template P
-   * @static
-   *
-   * @param {!PrimitiveWrapper<P>} wrapper
-   */
-  static registerPrimitiveWrapper(wrapper) {
-    if (!wrapper) {
-      throw new SecurityException('primitive wrapper cannot be null');
-    }
-    const primitiveType = wrapper.getPrimitiveType();
-    if (!primitiveType) {
-      throw new SecurityException('primitive wrapper cannot be undefined');
-    }
-
-    if (Registry.primitiveTypeToWrapper_.has(primitiveType)) {
-      // Cannot overwrite the existing key manager by a new one.
-      if (!(Registry.primitiveTypeToWrapper_.get(primitiveType) instanceof
-            wrapper.constructor)) {
-        throw new SecurityException(
-            'primitive wrapper for type ' + primitiveType +
-            ' has already been registered and cannot be overwritten');
-      }
-    }
-
-    Registry.primitiveTypeToWrapper_.set(primitiveType, wrapper);
-  }
-
-  /**
-   * Wraps a PrimitiveSet and returns a single instance.
-   *
-   * @template P
-   * @static
-   *
-   * @param {!PrimitiveSet.PrimitiveSet<P>} primitiveSet
-   * @return {!P}
-   */
-  static wrap(primitiveSet) {
-    if (!primitiveSet) {
-      throw new SecurityException('primitive set cannot be null.');
-    }
-    const primitiveType = primitiveSet.getPrimitiveType();
-    const wrapper = Registry.primitiveTypeToWrapper_.get(primitiveType);
-    if (!wrapper) {
-      throw new SecurityException(
-          'no primitive wrapper found for type ' + primitiveType);
-    }
-    return wrapper.wrap(primitiveSet);
-  }
-}
-// key managers maps
-/**
- * @static @private {!Map<string,!KeyManager.KeyManager>}
- *
- */
-Registry.typeToManagerMap_ = new Map();
-/**
- * @static @private {!Map<string,boolean>}
- */
-Registry.typeToNewKeyAllowedMap_ = new Map();
-
-// primitive wrappers map
-/**
- * @static @private {!Map<!Object,!PrimitiveWrapper>}
- */
-Registry.primitiveTypeToWrapper_ = new Map();
-
-exports = Registry;
diff --git a/javascript/signature/ecdsa_for_signing.ts b/javascript/signature/ecdsa_for_signing.ts
index e0da03d..4988bfc 100644
--- a/javascript/signature/ecdsa_for_signing.ts
+++ b/javascript/signature/ecdsa_for_signing.ts
@@ -1,7 +1,8 @@
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
 import EcdsaPrivateKeyManager from 'goog:tink.signature.EcdsaPrivateKeyManager'; // from //third_party/tink/javascript/signature:ecdsa_key_managers
 import SignatureKeyTemplates from 'goog:tink.signature.SignatureKeyTemplates'; // from //third_party/tink/javascript/signature:key_templates
 
+import * as Registry from '../internal/registry';
+
 export function register() {
   Registry.registerKeyManager(new EcdsaPrivateKeyManager());
 }
diff --git a/javascript/signature/ecdsa_for_verifying.ts b/javascript/signature/ecdsa_for_verifying.ts
index dae92fd..7273791 100644
--- a/javascript/signature/ecdsa_for_verifying.ts
+++ b/javascript/signature/ecdsa_for_verifying.ts
@@ -1,6 +1,7 @@
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
 import EcdsaPublicKeyManager from 'goog:tink.signature.EcdsaPublicKeyManager'; // from //third_party/tink/javascript/signature:ecdsa_key_managers
 
+import * as Registry from '../internal/registry';
+
 export function register() {
   Registry.registerKeyManager(new EcdsaPublicKeyManager());
 }
diff --git a/javascript/signature/ecdsa_private_key_manager.js b/javascript/signature/ecdsa_private_key_manager.js
index 15478ab..dc32450 100644
--- a/javascript/signature/ecdsa_private_key_manager.js
+++ b/javascript/signature/ecdsa_private_key_manager.js
@@ -14,15 +14,15 @@
 
 goog.module('tink.signature.EcdsaPrivateKeyManager');
 
-const Bytes = goog.require('tink.subtle.Bytes');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
 const EcdsaPublicKeyManager = goog.require('tink.signature.EcdsaPublicKeyManager');
-const EcdsaSign = goog.require('tink.subtle.EcdsaSign');
+const ecdsaSign = goog.require('google3.third_party.tink.javascript.subtle.ecdsa_sign');
 const EcdsaUtil = goog.require('tink.signature.EcdsaUtil');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const KeyManager = goog.require('tink.KeyManager');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
 const {PublicKeySign} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_sign');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Util = goog.require('tink.Util');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbEcdsaKeyFormat, PbEcdsaParams, PbEcdsaPrivateKey, PbEcdsaPublicKey, PbKeyData, PbMessage} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 /**
@@ -189,12 +189,12 @@
         keyProto, EcdsaPrivateKeyManager.VERSION_,
         EcdsaPublicKeyManager.VERSION);
 
-    const recepientPrivateKey = EcdsaUtil.getJsonWebKeyFromProto(keyProto);
+    const recipientPrivateKey = EcdsaUtil.getJsonWebKeyFromProto(keyProto);
     const params =
         /** @type {!PbEcdsaParams} */ (keyProto.getPublicKey().getParams());
     const hash = Util.hashTypeProtoToString(params.getHashType());
     const encoding = EcdsaUtil.encodingTypeProtoToEnum(params.getEncoding());
-    return await EcdsaSign.newInstance(recepientPrivateKey, hash, encoding);
+    return await ecdsaSign.fromJsonWebKey(recipientPrivateKey, hash, encoding);
   }
 
   /** @override */
diff --git a/javascript/signature/ecdsa_private_key_manager_test.js b/javascript/signature/ecdsa_private_key_manager_test.js
index 0627414..b249bec 100644
--- a/javascript/signature/ecdsa_private_key_manager_test.js
+++ b/javascript/signature/ecdsa_private_key_manager_test.js
@@ -17,11 +17,11 @@
 
 const EcdsaPrivateKeyManager = goog.require('tink.signature.EcdsaPrivateKeyManager');
 const EcdsaPublicKeyManager = goog.require('tink.signature.EcdsaPublicKeyManager');
-const KeyManager = goog.require('tink.KeyManager');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
 const {PublicKeySign} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_sign');
 const {PublicKeyVerify} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_verify');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const {PbEcdsaKeyFormat, PbEcdsaParams, PbEcdsaPrivateKey, PbEcdsaPublicKey, PbEcdsaSignatureEncoding, PbEllipticCurveType, PbHashType, PbKeyData} = goog.require('google3.third_party.tink.javascript.internal.proto');
 const {assertExists, assertInstanceof} = goog.require('google3.third_party.tink.javascript.testing.internal.test_utils');
 
diff --git a/javascript/signature/ecdsa_public_key_manager.js b/javascript/signature/ecdsa_public_key_manager.js
index 551e235..829cd0f 100644
--- a/javascript/signature/ecdsa_public_key_manager.js
+++ b/javascript/signature/ecdsa_public_key_manager.js
@@ -15,11 +15,11 @@
 goog.module('tink.signature.EcdsaPublicKeyManager');
 
 const EcdsaUtil = goog.require('tink.signature.EcdsaUtil');
-const EcdsaVerify = goog.require('tink.subtle.EcdsaVerify');
-const KeyManager = goog.require('tink.KeyManager');
+const ecdsaVerify = goog.require('google3.third_party.tink.javascript.subtle.ecdsa_verify');
+const KeyManager = goog.require('google3.third_party.tink.javascript.internal.key_manager');
 const {PublicKeyVerify} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_verify');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Util = goog.require('tink.Util');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbEcdsaParams, PbEcdsaPublicKey, PbKeyData, PbMessage} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 /**
@@ -67,7 +67,7 @@
     const params = /** @type{!PbEcdsaParams} */ (keyProto.getParams());
     const hash = Util.hashTypeProtoToString(params.getHashType());
     const encoding = EcdsaUtil.encodingTypeProtoToEnum(params.getEncoding());
-    return await EcdsaVerify.newInstance(jwk, hash, encoding);
+    return await ecdsaVerify.fromJsonWebKey(jwk, hash, encoding);
   }
 
   /** @override */
diff --git a/javascript/signature/ecdsa_public_key_manager_test.js b/javascript/signature/ecdsa_public_key_manager_test.js
index 5c442f7..f63713b 100644
--- a/javascript/signature/ecdsa_public_key_manager_test.js
+++ b/javascript/signature/ecdsa_public_key_manager_test.js
@@ -15,13 +15,13 @@
 goog.module('tink.signature.EcdsaPublicKeyManagerTest');
 goog.setTestOnly('tink.signature.EcdsaPublicKeyManagerTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
 const EcdsaPublicKeyManager = goog.require('tink.signature.EcdsaPublicKeyManager');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
 const {Mac} = goog.require('google3.third_party.tink.javascript.mac.internal.mac');
 const {PublicKeyVerify} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_verify');
-const Registry = goog.require('tink.Registry');
-const Util = goog.require('tink.Util');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
 const {PbEcdsaParams, PbEcdsaPublicKey, PbEcdsaSignatureEncoding, PbEllipticCurveType, PbHashType, PbKeyData} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 const KEY_TYPE = 'type.googleapis.com/google.crypto.tink.EcdsaPublicKey';
diff --git a/javascript/signature/ecdsa_util.js b/javascript/signature/ecdsa_util.js
index 2041fa5..f67f0fd 100644
--- a/javascript/signature/ecdsa_util.js
+++ b/javascript/signature/ecdsa_util.js
@@ -14,10 +14,10 @@
 
 goog.module('tink.signature.EcdsaUtil');
 
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Util = goog.require('tink.Util');
-const Validators = goog.require('tink.subtle.Validators');
+const Util = goog.require('google3.third_party.tink.javascript.internal.util');
+const Validators = goog.require('google3.third_party.tink.javascript.subtle.validators');
 const {PbEcdsaKeyFormat, PbEcdsaParams, PbEcdsaPrivateKey, PbEcdsaPublicKey, PbEcdsaSignatureEncoding: PbEcdsaSignatureEncodingType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 /**
diff --git a/javascript/signature/public_key_sign_wrapper.js b/javascript/signature/public_key_sign_wrapper.js
index 40f52c0..774479b 100644
--- a/javascript/signature/public_key_sign_wrapper.js
+++ b/javascript/signature/public_key_sign_wrapper.js
@@ -14,12 +14,12 @@
 
 goog.module('tink.signature.PublicKeySignWrapper');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const PrimitiveWrapper = goog.require('tink.PrimitiveWrapper');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
+const {PrimitiveWrapper} = goog.require('google3.third_party.tink.javascript.internal.primitive_wrapper');
 const {PublicKeySign} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_sign');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
+const Validators = goog.require('google3.third_party.tink.javascript.subtle.validators');
 
 /**
  * @final
diff --git a/javascript/signature/public_key_sign_wrapper_test.js b/javascript/signature/public_key_sign_wrapper_test.js
index 06f27b4..45d046d 100644
--- a/javascript/signature/public_key_sign_wrapper_test.js
+++ b/javascript/signature/public_key_sign_wrapper_test.js
@@ -15,11 +15,11 @@
 goog.module('tink.signature.PublicKeySignWrapperTest');
 goog.setTestOnly('tink.signature.PublicKeySignWrapperTest');
 
-const CryptoFormat = goog.require('tink.CryptoFormat');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
+const {CryptoFormat} = goog.require('google3.third_party.tink.javascript.internal.crypto_format');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
 const {PublicKeySign} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_sign');
 const PublicKeySignWrapper = goog.require('tink.signature.PublicKeySignWrapper');
-const Random = goog.require('tink.subtle.Random');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
 const {PbKeyStatusType, PbKeysetKey, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 describe('public key sign wrapper test', function() {
diff --git a/javascript/signature/public_key_verify_wrapper.js b/javascript/signature/public_key_verify_wrapper.js
index 4c4b9c2..cf8b2d2 100644
--- a/javascript/signature/public_key_verify_wrapper.js
+++ b/javascript/signature/public_key_verify_wrapper.js
@@ -14,12 +14,12 @@
 
 goog.module('tink.signature.PublicKeyVerifyWrapper');
 
-const CryptoFormat = goog.require('tink.CryptoFormat');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
-const PrimitiveWrapper = goog.require('tink.PrimitiveWrapper');
+const {CryptoFormat} = goog.require('google3.third_party.tink.javascript.internal.crypto_format');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
+const {PrimitiveWrapper} = goog.require('google3.third_party.tink.javascript.internal.primitive_wrapper');
 const {PublicKeyVerify} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_verify');
 const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
+const Validators = goog.require('google3.third_party.tink.javascript.subtle.validators');
 const {PbKeyStatusType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 /**
diff --git a/javascript/signature/public_key_verify_wrapper_test.js b/javascript/signature/public_key_verify_wrapper_test.js
index f793a57..42fcb18 100644
--- a/javascript/signature/public_key_verify_wrapper_test.js
+++ b/javascript/signature/public_key_verify_wrapper_test.js
@@ -15,13 +15,13 @@
 goog.module('tink.signature.PublicKeyVerifyWrapperTest');
 goog.setTestOnly('tink.signature.PublicKeyVerifyWrapperTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const PrimitiveSet = goog.require('tink.PrimitiveSet');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const PrimitiveSet = goog.require('google3.third_party.tink.javascript.internal.primitive_set');
 const {PublicKeySign} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_sign');
 const PublicKeySignWrapper = goog.require('tink.signature.PublicKeySignWrapper');
 const {PublicKeyVerify} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_verify');
 const PublicKeyVerifyWrapper = goog.require('tink.signature.PublicKeyVerifyWrapper');
-const Random = goog.require('tink.subtle.Random');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
 const {PbKeyStatusType, PbKeysetKey, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
 
 describe('public key verify wrapper test', function() {
diff --git a/javascript/signature/sign_wrapper.ts b/javascript/signature/sign_wrapper.ts
index 20baf89..75a1da1 100644
--- a/javascript/signature/sign_wrapper.ts
+++ b/javascript/signature/sign_wrapper.ts
@@ -1,6 +1,7 @@
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
 import PublicKeySignWrapper from 'goog:tink.signature.PublicKeySignWrapper'; // from //third_party/tink/javascript/signature:wrappers
 
+import * as Registry from '../internal/registry';
+
 export function register() {
   Registry.registerPrimitiveWrapper(new PublicKeySignWrapper());
 }
diff --git a/javascript/signature/signature_config.js b/javascript/signature/signature_config.js
index 3f987b2..93c7e0b 100644
--- a/javascript/signature/signature_config.js
+++ b/javascript/signature/signature_config.js
@@ -18,7 +18,7 @@
 const EcdsaPublicKeyManager = goog.require('tink.signature.EcdsaPublicKeyManager');
 const PublicKeySignWrapper = goog.require('tink.signature.PublicKeySignWrapper');
 const PublicKeyVerifyWrapper = goog.require('tink.signature.PublicKeyVerifyWrapper');
-const Registry = goog.require('tink.Registry');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 
 // Static methods and constants for registering with the Registry all instances
 // of key types for digital signature supported in a particular release of Tink.
diff --git a/javascript/signature/signature_config_test.js b/javascript/signature/signature_config_test.js
index 4f2cf57..c009f0f 100644
--- a/javascript/signature/signature_config_test.js
+++ b/javascript/signature/signature_config_test.js
@@ -17,11 +17,11 @@
 
 const EcdsaPrivateKeyManager = goog.require('tink.signature.EcdsaPrivateKeyManager');
 const EcdsaPublicKeyManager = goog.require('tink.signature.EcdsaPublicKeyManager');
-const KeysetHandle = goog.require('tink.KeysetHandle');
+const {KeysetHandle} = goog.require('google3.third_party.tink.javascript.internal.keyset_handle');
 const {PublicKeySign} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_sign');
 const {PublicKeyVerify} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_verify');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const SignatureConfig = goog.require('tink.signature.SignatureConfig');
 const SignatureKeyTemplates = goog.require('tink.signature.SignatureKeyTemplates');
 const {PbKeyData, PbKeyStatusType, PbKeyTemplate, PbKeyset, PbOutputPrefixType} = goog.require('google3.third_party.tink.javascript.internal.proto');
diff --git a/javascript/signature/subtle/index.ts b/javascript/signature/subtle/index.ts
index 8fbee8e..6f2dcd1 100644
--- a/javascript/signature/subtle/index.ts
+++ b/javascript/signature/subtle/index.ts
@@ -1,2 +1,2 @@
-export {default as EcdsaSign} from 'goog:tink.subtle.EcdsaSign';  // from //third_party/tink/javascript/subtle:signature
-export {EcdsaSignatureEncodingType, exportCryptoKey, generateKeyPair, importPrivateKey, importPublicKey} from 'goog:tink.subtle.EllipticCurves';  // from //third_party/tink/javascript/subtle
+export {EcdsaSign, fromJsonWebKey as ecdsaSignFromJsonWebKey} from '../../subtle/ecdsa_sign';
+export {EcdsaSignatureEncodingType, exportCryptoKey, generateKeyPair, importPrivateKey, importPublicKey} from '../../subtle/elliptic_curves';
diff --git a/javascript/signature/verify_wrapper.ts b/javascript/signature/verify_wrapper.ts
index d560268..5c8a01b 100644
--- a/javascript/signature/verify_wrapper.ts
+++ b/javascript/signature/verify_wrapper.ts
@@ -1,6 +1,7 @@
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
 import PublicKeyVerifyWrapper from 'goog:tink.signature.PublicKeyVerifyWrapper'; // from //third_party/tink/javascript/signature:wrappers
 
+import * as Registry from '../internal/registry';
+
 export function register() {
   Registry.registerPrimitiveWrapper(new PublicKeyVerifyWrapper());
 }
diff --git a/javascript/subtle/aes_ctr.js b/javascript/subtle/aes_ctr.js
deleted file mode 100644
index 514133f..0000000
--- a/javascript/subtle/aes_ctr.js
+++ /dev/null
@@ -1,113 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.AesCtr');
-
-const Bytes = goog.require('tink.subtle.Bytes');
-const IndCpaCipher = goog.require('tink.subtle.IndCpaCipher');
-const Random = goog.require('tink.subtle.Random');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
-
-/**
- * The minimum IV size.
- *
- * @const {number}
- */
-const MIN_IV_SIZE_IN_BYTES = 12;
-
-/**
- * AES block size.
- *
- * @const {number}
- */
-const AES_BLOCK_SIZE_IN_BYTES = 16;
-
-/**
- * Implementation of AES-CTR.
- *
- * @implements {IndCpaCipher}
- * @protected
- * @final
- */
-class AesCtr {
-  /**
-   * @param {!webCrypto.CryptoKey} key
-   * @param {number} ivSize the size of the IV
-   */
-  constructor(key, ivSize) {
-    /** @const @private {!webCrypto.CryptoKey} */
-    this.key_ = key;
-
-    /** @const @private {number} */
-    this.ivSize_ = ivSize;
-  }
-
-  /**
-   * @param {!Uint8Array} key
-   * @param {number} ivSize the size of the IV, must be larger than or equal to
-   *     {@link MIN_IV_SIZE_IN_BYTES}
-   * @return {!Promise.<!IndCpaCipher>}
-   * @static
-   */
-  static async newInstance(key, ivSize) {
-    if (!Number.isInteger(ivSize)) {
-      throw new SecurityException('invalid IV length, must be an integer');
-    }
-    if (ivSize < MIN_IV_SIZE_IN_BYTES || ivSize > AES_BLOCK_SIZE_IN_BYTES) {
-      throw new SecurityException(
-          'invalid IV length, must be at least ' + MIN_IV_SIZE_IN_BYTES +
-          ' and at most ' + AES_BLOCK_SIZE_IN_BYTES);
-    }
-    Validators.requireUint8Array(key);
-    Validators.validateAesKeySize(key.length);
-
-    const cryptoKey = await self.crypto.subtle.importKey(
-        'raw', key, {'name': 'AES-CTR', 'length': key.length}, false,
-        ['encrypt', 'decrypt']);
-
-    return new AesCtr(cryptoKey, ivSize);
-  }
-
-  /**
-   * @override
-   */
-  async encrypt(plaintext) {
-    Validators.requireUint8Array(plaintext);
-    const iv = Random.randBytes(this.ivSize_);
-    const counter = new Uint8Array(AES_BLOCK_SIZE_IN_BYTES);
-    counter.set(iv);
-    const alg = {'name': 'AES-CTR', 'counter': counter, 'length': 128};
-    const ciphertext =
-        await self.crypto.subtle.encrypt(alg, this.key_, plaintext);
-    return Bytes.concat(iv, new Uint8Array(ciphertext));
-  }
-
-  /**
-   * @override
-   */
-  async decrypt(ciphertext) {
-    Validators.requireUint8Array(ciphertext);
-    if (ciphertext.length < this.ivSize_) {
-      throw new SecurityException('ciphertext too short');
-    }
-    const counter = new Uint8Array(AES_BLOCK_SIZE_IN_BYTES);
-    counter.set(ciphertext.subarray(0, this.ivSize_));
-    const alg = {'name': 'AES-CTR', 'counter': counter, 'length': 128};
-    return new Uint8Array(await self.crypto.subtle.decrypt(
-        alg, this.key_, new Uint8Array(ciphertext.subarray(this.ivSize_))));
-  }
-}
-
-exports = AesCtr;
diff --git a/javascript/subtle/aes_ctr.ts b/javascript/subtle/aes_ctr.ts
new file mode 100644
index 0000000..68a6a4a
--- /dev/null
+++ b/javascript/subtle/aes_ctr.ts
@@ -0,0 +1,92 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {SecurityException} from '../exception/security_exception';
+
+import * as Bytes from './bytes';
+import {IndCpaCipher} from './ind_cpa_cipher';
+import * as Random from './random';
+import * as Validators from './validators';
+
+/**
+ * The minimum IV size.
+ *
+ */
+const MIN_IV_SIZE_IN_BYTES: number = 12;
+
+/**
+ * AES block size.
+ *
+ */
+const AES_BLOCK_SIZE_IN_BYTES: number = 16;
+
+/**
+ * Implementation of AES-CTR.
+ *
+ * @final
+ */
+export class AesCtr implements IndCpaCipher {
+  /**
+   * @param ivSize the size of the IV
+   */
+  constructor(
+      private readonly key: CryptoKey, private readonly ivSize: number) {}
+
+  /**
+   * @override
+   */
+  async encrypt(plaintext: Uint8Array): Promise<Uint8Array> {
+    Validators.requireUint8Array(plaintext);
+    const iv = Random.randBytes(this.ivSize);
+    const counter = new Uint8Array(AES_BLOCK_SIZE_IN_BYTES);
+    counter.set(iv);
+    const alg = {'name': 'AES-CTR', 'counter': counter, 'length': 128};
+    const ciphertext =
+        await self.crypto.subtle.encrypt(alg, this.key, plaintext);
+    return Bytes.concat(iv, new Uint8Array(ciphertext));
+  }
+
+  /**
+   * @override
+   */
+  async decrypt(ciphertext: Uint8Array): Promise<Uint8Array> {
+    Validators.requireUint8Array(ciphertext);
+    if (ciphertext.length < this.ivSize) {
+      throw new SecurityException('ciphertext too short');
+    }
+    const counter = new Uint8Array(AES_BLOCK_SIZE_IN_BYTES);
+    counter.set(ciphertext.subarray(0, this.ivSize));
+    const alg = {'name': 'AES-CTR', 'counter': counter, 'length': 128};
+    return new Uint8Array(await self.crypto.subtle.decrypt(
+        alg, this.key, new Uint8Array(ciphertext.subarray(this.ivSize))));
+  }
+}
+
+/**
+ * @param ivSize the size of the IV, must be larger than or equal to
+ *     {@link MIN_IV_SIZE_IN_BYTES}
+ */
+export async function fromRawKey(
+    key: Uint8Array, ivSize: number): Promise<IndCpaCipher> {
+  if (!Number.isInteger(ivSize)) {
+    throw new SecurityException('invalid IV length, must be an integer');
+  }
+  if (ivSize < MIN_IV_SIZE_IN_BYTES || ivSize > AES_BLOCK_SIZE_IN_BYTES) {
+    throw new SecurityException(
+        'invalid IV length, must be at least ' + MIN_IV_SIZE_IN_BYTES +
+        ' and at most ' + AES_BLOCK_SIZE_IN_BYTES);
+  }
+  Validators.requireUint8Array(key);
+  Validators.validateAesKeySize(key.length);
+  const cryptoKey = await self.crypto.subtle.importKey(
+      'raw', key, {'name': 'AES-CTR', 'length': key.length}, false,
+      ['encrypt', 'decrypt']);
+  return new AesCtr(cryptoKey, ivSize);
+}
diff --git a/javascript/subtle/aes_ctr_test.js b/javascript/subtle/aes_ctr_test.js
index 52dbcc1..eddccb2 100644
--- a/javascript/subtle/aes_ctr_test.js
+++ b/javascript/subtle/aes_ctr_test.js
@@ -15,9 +15,9 @@
 goog.module('tink.subtle.AesCtrTest');
 goog.setTestOnly('tink.subtle.AesCtrTest');
 
-const AesCtr = goog.require('tink.subtle.AesCtr');
-const Bytes = goog.require('tink.subtle.Bytes');
-const Random = goog.require('tink.subtle.Random');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const {fromRawKey: aesCtrFromRawKey} = goog.require('google3.third_party.tink.javascript.subtle.aes_ctr');
 
 describe('aes ctr test', function() {
   beforeEach(function() {
@@ -36,7 +36,7 @@
     const key = Random.randBytes(16);
     for (let i = 0; i < 100; i++) {
       const msg = Random.randBytes(20);
-      const cipher = await AesCtr.newInstance(key, 16);
+      const cipher = await aesCtrFromRawKey(key, 16);
       let ciphertext = await cipher.encrypt(msg);
       let plaintext = await cipher.decrypt(ciphertext);
       expect(Bytes.toHex(plaintext)).toBe(Bytes.toHex(msg));
@@ -44,7 +44,7 @@
   });
 
   it('probabilistic encryption', async function() {
-    const cipher = await AesCtr.newInstance(Random.randBytes(16), 16);
+    const cipher = await aesCtrFromRawKey(Random.randBytes(16), 16);
     const msg = Random.randBytes(20);
     const results = new Set();
     for (let i = 0; i < 100; i++) {
@@ -56,7 +56,7 @@
 
   it('constructor', async function() {
     try {
-      await AesCtr.newInstance(Random.randBytes(16), 11);  // IV size too short
+      await aesCtrFromRawKey(Random.randBytes(16), 11);  // IV size too short
       fail('Should throw an exception.');
     } catch (e) {
       expect(e.toString())
@@ -64,7 +64,7 @@
               'SecurityException: invalid IV length, must be at least 12 and at most 16');
     }
     try {
-      await AesCtr.newInstance(Random.randBytes(16), 17);  // IV size too long
+      await aesCtrFromRawKey(Random.randBytes(16), 17);  // IV size too long
       fail('Should throw an exception.');
     } catch (e) {
       expect(e.toString())
@@ -72,7 +72,7 @@
               'SecurityException: invalid IV length, must be at least 12 and at most 16');
     }
     try {
-      await AesCtr.newInstance(
+      await aesCtrFromRawKey(
           Random.randBytes(24), 12);  // 192-bit keys not supported
       fail('Should throw an exception.');
     } catch (e) {
@@ -83,7 +83,7 @@
 
   it('constructor, invalid iv sizes', async function() {
     try {
-      await AesCtr.newInstance(Random.randBytes(16), NaN);
+      await aesCtrFromRawKey(Random.randBytes(16), NaN);
       fail('Should throw an exception.');
     } catch (e) {
       expect(e.toString())
@@ -91,7 +91,7 @@
     }
 
     try {
-      await AesCtr.newInstance(Random.randBytes(16), 12.5);
+      await aesCtrFromRawKey(Random.randBytes(16), 12.5);
       fail('Should throw an exception.');
     } catch (e) {
       expect(e.toString())
@@ -99,7 +99,7 @@
     }
 
     try {
-      await AesCtr.newInstance(Random.randBytes(16), 0);
+      await aesCtrFromRawKey(Random.randBytes(16), 0);
       fail('Should throw an exception.');
     } catch (e) {
       expect(e.toString())
@@ -128,7 +128,7 @@
       const iv = Bytes.fromHex(testVector['iv']);
       const msg = Bytes.fromHex(testVector['message']);
       const ciphertext = Bytes.fromHex(testVector['ciphertext']);
-      const aesctr = await AesCtr.newInstance(key, iv.length);
+      const aesctr = await aesCtrFromRawKey(key, iv.length);
       const plaintext = await aesctr.decrypt(Bytes.concat(iv, ciphertext));
       expect(Bytes.toHex(plaintext)).toBe(Bytes.toHex(msg));
     }
diff --git a/javascript/subtle/aes_gcm.js b/javascript/subtle/aes_gcm.js
deleted file mode 100644
index db6cf11..0000000
--- a/javascript/subtle/aes_gcm.js
+++ /dev/null
@@ -1,122 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.AesGcm');
-
-const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
-const Bytes = goog.require('tink.subtle.Bytes');
-const Random = goog.require('tink.subtle.Random');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
-
-/**
- * The only supported IV size.
- *
- * @const {number}
- */
-const IV_SIZE_IN_BYTES = 12;
-
-/**
- * The only supported tag size.
- *
- * @const {number}
- */
-const TAG_SIZE_IN_BITS = 128;
-
-/**
- * Implementation of AES-GCM.
- *
- * @public
- * @final
- */
-class AesGcm extends Aead {
-  /**
-   * @param {!webCrypto.CryptoKey} key
-   */
-  constructor(key) {
-    super();
-    /** @const @private {!webCrypto.CryptoKey} */
-    this.key_ = key;
-  }
-
-  /**
-   * @param {!Uint8Array} key
-   * @return {!Promise.<!Aead>}
-   * @static
-   */
-  static async newInstance(key) {
-    Validators.requireUint8Array(key);
-    Validators.validateAesKeySize(key.length);
-
-    const webCryptoKey = await self.crypto.subtle.importKey(
-        'raw' /* format */, key /* keyData */,
-        {'name': 'AES-GCM', 'length': key.length} /* algo */,
-        false /* extractable*/, ['encrypt', 'decrypt'] /* usage */);
-    return new AesGcm(webCryptoKey);
-  }
-
-  /**
-   * @override
-   */
-  async encrypt(plaintext, opt_associatedData) {
-    Validators.requireUint8Array(plaintext);
-    if (opt_associatedData != null) {
-      Validators.requireUint8Array(opt_associatedData);
-    }
-    const iv = Random.randBytes(IV_SIZE_IN_BYTES);
-    const alg = {
-      'name': 'AES-GCM',
-      'iv': iv,
-      'tagLength': TAG_SIZE_IN_BITS,
-    };
-    if (opt_associatedData) {
-      alg['additionalData'] = opt_associatedData;
-    }
-    const ciphertext =
-        await self.crypto.subtle.encrypt(alg, this.key_, plaintext);
-    return Bytes.concat(iv, new Uint8Array(ciphertext));
-  }
-
-  /**
-   * @override
-   */
-  async decrypt(ciphertext, opt_associatedData) {
-    Validators.requireUint8Array(ciphertext);
-    if (ciphertext.length < IV_SIZE_IN_BYTES + TAG_SIZE_IN_BITS / 8) {
-      throw new SecurityException('ciphertext too short');
-    }
-    if (opt_associatedData != null) {
-      Validators.requireUint8Array(opt_associatedData);
-    }
-    const iv = new Uint8Array(IV_SIZE_IN_BYTES);
-    iv.set(ciphertext.subarray(0, IV_SIZE_IN_BYTES));
-    const alg = {
-      'name': 'AES-GCM',
-      'iv': iv,
-      'tagLength': TAG_SIZE_IN_BITS,
-    };
-    if (opt_associatedData) {
-      alg['additionalData'] = opt_associatedData;
-    }
-    try {
-      return new Uint8Array(await self.crypto.subtle.decrypt(
-          alg, this.key_,
-          new Uint8Array(ciphertext.subarray(IV_SIZE_IN_BYTES))));
-    } catch (e) {
-      throw new SecurityException(e.toString());
-    }
-  }
-}
-
-exports = AesGcm;
diff --git a/javascript/subtle/aes_gcm.ts b/javascript/subtle/aes_gcm.ts
new file mode 100644
index 0000000..4e53f58
--- /dev/null
+++ b/javascript/subtle/aes_gcm.ts
@@ -0,0 +1,108 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {Aead} from '../aead/internal/aead';
+import {SecurityException} from '../exception/security_exception';
+
+import * as Bytes from './bytes';
+import * as Random from './random';
+import * as Validators from './validators';
+
+/**
+ * The only supported IV size.
+ *
+ */
+const IV_SIZE_IN_BYTES: number = 12;
+
+/**
+ * The only supported tag size.
+ *
+ */
+const TAG_SIZE_IN_BITS: number = 128;
+
+/**
+ * Implementation of AES-GCM.
+ *
+ * @final
+ */
+export class AesGcm implements Aead {
+  constructor(private readonly key: CryptoKey) {}
+
+  /**
+   * @override
+   */
+  async encrypt(plaintext: Uint8Array, associatedData?: Uint8Array):
+      Promise<Uint8Array> {
+    Validators.requireUint8Array(plaintext);
+    if (associatedData != null) {
+      Validators.requireUint8Array(associatedData);
+    }
+    const iv = Random.randBytes(IV_SIZE_IN_BYTES);
+    const alg: AesGcmParams = {
+      'name': 'AES-GCM',
+      'iv': iv,
+      'tagLength': TAG_SIZE_IN_BITS
+    };
+    if (associatedData) {
+      alg['additionalData'] = associatedData;
+    }
+    const ciphertext =
+        await self.crypto.subtle.encrypt(alg, this.key, plaintext);
+    return Bytes.concat(iv, new Uint8Array(ciphertext));
+  }
+
+  /**
+   * @override
+   */
+  async decrypt(ciphertext: Uint8Array, associatedData?: Uint8Array):
+      Promise<Uint8Array> {
+    Validators.requireUint8Array(ciphertext);
+    if (ciphertext.length < IV_SIZE_IN_BYTES + TAG_SIZE_IN_BITS / 8) {
+      throw new SecurityException('ciphertext too short');
+    }
+    if (associatedData != null) {
+      Validators.requireUint8Array(associatedData);
+    }
+    const iv = new Uint8Array(IV_SIZE_IN_BYTES);
+    iv.set(ciphertext.subarray(0, IV_SIZE_IN_BYTES));
+    const alg: AesGcmParams = {
+      'name': 'AES-GCM',
+      'iv': iv,
+      'tagLength': TAG_SIZE_IN_BITS
+    };
+    if (associatedData) {
+      alg['additionalData'] = associatedData;
+    }
+    try {
+      return new Uint8Array(await self.crypto.subtle.decrypt(
+          alg, this.key,
+          new Uint8Array(ciphertext.subarray(IV_SIZE_IN_BYTES))));
+    } catch (e) {
+      throw new SecurityException(e.toString());
+    }
+  }
+}
+
+export async function fromRawKey(key: Uint8Array): Promise<Aead> {
+  Validators.requireUint8Array(key);
+  Validators.validateAesKeySize(key.length);
+  const webCryptoKey = await self.crypto.subtle.importKey(
+      /* format */
+      'raw', key,
+      /* keyData */
+      {'name': 'AES-GCM', 'length': key.length},
+      /* algo */
+      false,
+      /* extractable*/
+      ['encrypt', 'decrypt']);
+
+  /* usage */
+  return new AesGcm(webCryptoKey);
+}
diff --git a/javascript/subtle/aes_gcm_test.js b/javascript/subtle/aes_gcm_test.js
index 027ba30..0a7f6b4 100644
--- a/javascript/subtle/aes_gcm_test.js
+++ b/javascript/subtle/aes_gcm_test.js
@@ -15,9 +15,9 @@
 goog.module('tink.subtle.AesGcmTest');
 goog.setTestOnly('tink.subtle.AesGcmTest');
 
-const AesGcm = goog.require('tink.subtle.AesGcm');
-const Bytes = goog.require('tink.subtle.Bytes');
-const Random = goog.require('tink.subtle.Random');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const {fromRawKey: aesGcmFromRawKey} = goog.require('google3.third_party.tink.javascript.subtle.aes_gcm');
 
 /**
  * Asserts that an exception is the result of a Web Crypto error.
@@ -41,7 +41,7 @@
   });
 
   it('basic', async function() {
-    const aead = await AesGcm.newInstance(Random.randBytes(16));
+    const aead = await aesGcmFromRawKey(Random.randBytes(16));
     for (let i = 0; i < 100; i++) {
       const msg = Random.randBytes(i);
       let ciphertext = await aead.encrypt(msg);
@@ -64,7 +64,7 @@
   });
 
   it('probabilistic encryption', async function() {
-    const aead = await AesGcm.newInstance(Random.randBytes(16));
+    const aead = await aesGcmFromRawKey(Random.randBytes(16));
     const msg = Random.randBytes(20);
     const aad = Random.randBytes(20);
     const results = new Set();
@@ -76,7 +76,7 @@
   });
 
   it('bit flip ciphertext', async function() {
-    const aead = await AesGcm.newInstance(Random.randBytes(16));
+    const aead = await aesGcmFromRawKey(Random.randBytes(16));
     const plaintext = Random.randBytes(8);
     const aad = Random.randBytes(8);
     const ciphertext = await aead.encrypt(plaintext, aad);
@@ -95,7 +95,7 @@
   });
 
   it('bit flip aad', async function() {
-    const aead = await AesGcm.newInstance(Random.randBytes(16));
+    const aead = await aesGcmFromRawKey(Random.randBytes(16));
     const plaintext = Random.randBytes(8);
     const aad = Random.randBytes(8);
     const ciphertext = await aead.encrypt(plaintext, aad);
@@ -114,7 +114,7 @@
   });
 
   it('truncation', async function() {
-    const aead = await AesGcm.newInstance(Random.randBytes(16));
+    const aead = await aesGcmFromRawKey(Random.randBytes(16));
     const plaintext = Random.randBytes(8);
     const aad = Random.randBytes(8);
     const ciphertext = await aead.encrypt(plaintext, aad);
@@ -626,7 +626,7 @@
         ];
     for (let i = 0; i < NIST_TEST_VECTORS.length; i++) {
       const testVector = NIST_TEST_VECTORS[i];
-      const aead = await AesGcm.newInstance(Bytes.fromHex(testVector['Key']));
+      const aead = await aesGcmFromRawKey(Bytes.fromHex(testVector['Key']));
       const ciphertext = Bytes.fromHex(
           testVector['IV'] + testVector['CT'] + testVector['Tag']);
       const aad = Bytes.fromHex(testVector['AAD']);
diff --git a/javascript/subtle/bytes.js b/javascript/subtle/bytes.ts
similarity index 61%
rename from javascript/subtle/bytes.js
rename to javascript/subtle/bytes.ts
index 51d997e..26b8912 100644
--- a/javascript/subtle/bytes.js
+++ b/javascript/subtle/bytes.ts
@@ -1,28 +1,22 @@
 // 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.
-//
 ////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.Bytes');
-
-const {InvalidArgumentsException} = goog.require('google3.third_party.tink.javascript.exception.invalid_arguments_exception');
+import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
 
 /**
  * Does near constant time byte array comparison.
- * @param {!Uint8Array} ba1 The first bytearray to check.
- * @param {!Uint8Array} ba2 The second bytearray to check.
- * @return {boolean} If the array are equal.
+ * @param ba1 The first bytearray to check.
+ * @param ba2 The second bytearray to check.
+ * @return If the array are equal.
  */
-const isEqual = function(ba1, ba2) {
+export function isEqual(ba1: Uint8Array, ba2: Uint8Array): boolean {
   if (ba1.length !== ba2.length) {
     return false;
   }
@@ -31,36 +25,33 @@
     result |= ba1[i] ^ ba2[i];
   }
   return result == 0;
-};
-
+}
 
 /**
  * Returns a new array that is the result of joining the arguments.
- * @param {...!Uint8Array} var_args
- * @return {!Uint8Array}
  */
-const concat = function(var_args) {
+export function concat(...var_args: Uint8Array[]): Uint8Array {
   let length = 0;
   for (let i = 0; i < arguments.length; i++) {
     length += arguments[i].length;
   }
-  let result = new Uint8Array(length);
+  const result = new Uint8Array(length);
   let curOffset = 0;
   for (let i = 0; i < arguments.length; i++) {
     result.set(arguments[i], curOffset);
     curOffset += arguments[i].length;
   }
   return result;
-};
+}
 
 /**
  * Converts a non-negative integer number to a 64-bit big-endian byte array.
- * @param {number} value The number to convert.
- * @return {!Uint8Array} The number as a big-endian byte array.
+ * @param value The number to convert.
+ * @return The number as a big-endian byte array.
  * @throws {InvalidArgumentsException}
  * @static
  */
-const fromNumber = function(value) {
+export function fromNumber(value: number): Uint8Array {
   if (isNaN(value) || value % 1 !== 0) {
     throw new InvalidArgumentsException('cannot convert non-integer value');
   }
@@ -71,135 +62,126 @@
     throw new InvalidArgumentsException(
         'cannot convert number larger than ' + Number.MAX_SAFE_INTEGER);
   }
-  const two_power_32 = 2**32;
-  let low = value % two_power_32;
-  let high = value / two_power_32;
+  const twoPower32 = 2 ** 32;
+  let low = value % twoPower32;
+  let high = value / twoPower32;
   const result = new Uint8Array(8);
   for (let i = 7; i >= 4; i--) {
-    result[i] = low & 0xff;
+    result[i] = low & 255;
     low >>>= 8;
   }
   for (let i = 3; i >= 0; i--) {
-    result[i] = high & 0xff;
+    result[i] = high & 255;
     high >>>= 8;
   }
   return result;
-};
+}
 
 /**
  * Converts the hex string to a byte array.
  *
- * @param {string} hex the input
- * @return {!Uint8Array} the byte array output
+ * @param hex the input
+ * @return the byte array output
  * @throws {!InvalidArgumentsException}
  * @static
  */
-const fromHex = function(hex) {
+export function fromHex(hex: string): Uint8Array {
   if (hex.length % 2 != 0) {
     throw new InvalidArgumentsException(
         'Hex string length must be multiple of 2');
   }
-  var arr = new Uint8Array(hex.length / 2);
-  for (var i = 0; i < hex.length; i += 2) {
+  const arr = new Uint8Array(hex.length / 2);
+  for (let i = 0; i < hex.length; i += 2) {
     arr[i / 2] = parseInt(hex.substring(i, i + 2), 16);
   }
   return arr;
-};
+}
 
 /**
  * Converts a byte array to hex.
  *
- * @param {!Uint8Array} bytes the byte array input
- * @return {string} hex the output
+ * @param bytes the byte array input
+ * @return hex the output
  * @static
  */
-const toHex = function(bytes) {
+export function toHex(bytes: Uint8Array): string {
   let result = '';
   for (let i = 0; i < bytes.length; i++) {
-    let hexByte = bytes[i].toString(16);
+    const hexByte = bytes[i].toString(16);
     result += hexByte.length > 1 ? hexByte : '0' + hexByte;
   }
   return result;
-};
+}
 
 /**
  * Converts the Base64 string to a byte array.
  *
- * @param {string} encoded the base64 string
- * @param {boolean=} opt_webSafe True indicates we should use the alternative
+ * @param encoded the base64 string
+ * @param opt_webSafe True indicates we should use the alternative
  *     alphabet, which does not require escaping for use in URLs.
- * @return {!Uint8Array} the byte array output
+ * @return the byte array output
  * @static
  */
-const fromBase64 = function(encoded, opt_webSafe) {
+export function fromBase64(encoded: string, opt_webSafe?: boolean): Uint8Array {
   if (opt_webSafe) {
     const normalBase64 = encoded.replace(/-/g, '+').replace(/_/g, '/');
     return fromByteString(window.atob(normalBase64));
   }
   return fromByteString(window.atob(encoded));
-};
+}
 
 /**
  * Base64 encode a byte array.
  *
- * @param {!Uint8Array} bytes the byte array input
- * @param {boolean=} opt_webSafe True indicates we should use the alternative
+ * @param bytes the byte array input
+ * @param opt_webSafe True indicates we should use the alternative
  *     alphabet, which does not require escaping for use in URLs.
- * @return {string} base64 output
+ * @return base64 output
  * @static
  */
-const toBase64 = function(bytes, opt_webSafe) {
-  let encoded =
-      window.btoa(toByteString(bytes)).replace(/=/g, '') /* padding */;
+export function toBase64(bytes: Uint8Array, opt_webSafe?: boolean): string {
+  const encoded = window
+                      .btoa(
+                          /* padding */
+                          toByteString(bytes))
+                      .replace(/=/g, '');
   if (opt_webSafe) {
     return encoded.replace(/\+/g, '-').replace(/\//g, '_');
   }
   return encoded;
-};
+}
 
 /**
  * Converts a byte string to a byte array. Only support ASCII and Latin-1
  * strings, does not support multi-byte characters.
  *
- * @param {string} str the input
- * @return {!Uint8Array} the byte array output
+ * @param str the input
+ * @return the byte array output
  * @static
  */
-const fromByteString = function(str) {
-  let output = [];
+export function fromByteString(str: string): Uint8Array {
+  const output = [];
   let p = 0;
   for (let i = 0; i < str.length; i++) {
-    let c = str.charCodeAt(i);
+    const c = str.charCodeAt(i);
     output[p++] = c;
   }
   return new Uint8Array(output);
-};
+}
 
 /**
  * Turns a byte array into the string given by the concatenation of the
  * characters to which the numbers correspond. Each byte is corresponding to a
  * character. Does not support multi-byte characters.
  *
- * @param {!Uint8Array} bytes Array of numbers representing
+ * @param bytes Array of numbers representing
  *     characters.
- * @return {string} Stringification of the array.
+ * @return Stringification of the array.
  */
-const toByteString = function(bytes) {
-  var str = '';
-  for (var i = 0; i < bytes.length; i += 1) {
+export function toByteString(bytes: Uint8Array): string {
+  let str = '';
+  for (let i = 0; i < bytes.length; i += 1) {
     str += String.fromCharCode(bytes[i]);
   }
   return str;
-};
-
-exports = {
-  concat,
-  fromBase64,
-  fromHex,
-  fromNumber,
-  fromByteString,
-  isEqual,
-  toBase64,
-  toHex,
-  toByteString,
-};
+}
diff --git a/javascript/subtle/bytes_test.js b/javascript/subtle/bytes_test.js
index 6108f8f..5ab1f3b 100644
--- a/javascript/subtle/bytes_test.js
+++ b/javascript/subtle/bytes_test.js
@@ -15,8 +15,8 @@
 goog.module('tink.subtle.BytesTest');
 goog.setTestOnly('tink.subtle.BytesTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const Random = goog.require('tink.subtle.Random');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
 
 describe('bytes test', function() {
   it('concat', function() {
diff --git a/javascript/subtle/ecdsa_sign.js b/javascript/subtle/ecdsa_sign.js
deleted file mode 100644
index adeff35..0000000
--- a/javascript/subtle/ecdsa_sign.js
+++ /dev/null
@@ -1,91 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.EcdsaSign');
-
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const {PublicKeySign} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_sign');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
-
-/**
- * Implementation of ECDSA signing.
- *
- * @public
- * @final
- */
-class EcdsaSign extends PublicKeySign {
-  /**
-   * @param {!webCrypto.CryptoKey} key
-   * @param {string} hash
-   * @param {?EllipticCurves.EcdsaSignatureEncodingType=} opt_encoding The
-   *     optional encoding of the signature. If absent, default is IEEE P1363.
-   */
-  constructor(key, hash, opt_encoding) {
-    super();
-
-    /** @const @private {!webCrypto.CryptoKey} */
-    this.key_ = key;
-
-    /** @const @private {string} */
-    this.hash_ = hash;
-
-    if (!opt_encoding) {
-      opt_encoding = EllipticCurves.EcdsaSignatureEncodingType.IEEE_P1363;
-    }
-
-    /** @const @private {!EllipticCurves.EcdsaSignatureEncodingType} */
-    this.encoding_ = opt_encoding;
-  }
-
-  /**
-   * @param {!webCrypto.JsonWebKey} jwk
-   * @param {string} hash
-   * @param {?EllipticCurves.EcdsaSignatureEncodingType=} opt_encoding The
-   *     optional encoding of the signature. If absent, default is IEEE P1363.
-   *
-   * @return {!Promise<!PublicKeySign>}
-   * @static
-   */
-  static async newInstance(jwk, hash, opt_encoding) {
-    if (!jwk) {
-      throw new SecurityException('private key has to be non-null');
-    }
-    Validators.validateEcdsaParams(jwk.crv, hash);
-    const cryptoKey = await EllipticCurves.importPrivateKey('ECDSA', jwk);
-    return new EcdsaSign(cryptoKey, hash, opt_encoding);
-  }
-
-  /**
-   * @override
-   */
-  async sign(data) {
-    Validators.requireUint8Array(data);
-    const signature = await window.crypto.subtle.sign(
-        {
-          name: 'ECDSA',
-          hash: {
-            name: this.hash_,
-          },
-        },
-        this.key_, data);
-
-    if (this.encoding_ == EllipticCurves.EcdsaSignatureEncodingType.DER) {
-      return EllipticCurves.ecdsaIeee2Der(new Uint8Array(signature));
-    }
-    return new Uint8Array(signature);
-  }
-}
-
-exports = EcdsaSign;
diff --git a/javascript/subtle/ecdsa_sign.ts b/javascript/subtle/ecdsa_sign.ts
new file mode 100644
index 0000000..fabdc1e
--- /dev/null
+++ b/javascript/subtle/ecdsa_sign.ts
@@ -0,0 +1,70 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {SecurityException} from '../exception/security_exception';
+import {PublicKeySign} from '../signature/internal/public_key_sign';
+
+import * as EllipticCurves from './elliptic_curves';
+import * as Validators from './validators';
+
+/**
+ * Implementation of ECDSA signing.
+ *
+ * @final
+ */
+export class EcdsaSign implements PublicKeySign {
+  private readonly encoding_: EllipticCurves.EcdsaSignatureEncodingType;
+
+  /**
+   * @param opt_encoding The
+   *     optional encoding of the signature. If absent, default is IEEE P1363.
+   */
+  constructor(
+      private readonly key: CryptoKey, private readonly hash: string,
+      opt_encoding?: EllipticCurves.EcdsaSignatureEncodingType|null) {
+    if (!opt_encoding) {
+      opt_encoding = EllipticCurves.EcdsaSignatureEncodingType.IEEE_P1363;
+    }
+    this.encoding_ = opt_encoding;
+  }
+
+  /**
+   * @override
+   */
+  async sign(message: Uint8Array): Promise<Uint8Array> {
+    Validators.requireUint8Array(message);
+    const signature = await window.crypto.subtle.sign(
+        {name: 'ECDSA', hash: {name: this.hash}}, this.key, message);
+    if (this.encoding_ == EllipticCurves.EcdsaSignatureEncodingType.DER) {
+      return EllipticCurves.ecdsaIeee2Der(new Uint8Array(signature));
+    }
+    return new Uint8Array(signature);
+  }
+}
+
+/**
+ * @param opt_encoding The
+ *     optional encoding of the signature. If absent, default is IEEE P1363.
+ */
+export async function fromJsonWebKey(
+    jwk: JsonWebKey, hash: string,
+    opt_encoding?: EllipticCurves.EcdsaSignatureEncodingType|
+    null): Promise<PublicKeySign> {
+  if (!jwk) {
+    throw new SecurityException('private key has to be non-null');
+  }
+  const {crv} = jwk;
+  if (!crv) {
+    throw new SecurityException('curve has to be defined');
+  }
+  Validators.validateEcdsaParams(crv, hash);
+  const cryptoKey = await EllipticCurves.importPrivateKey('ECDSA', jwk);
+  return new EcdsaSign(cryptoKey, hash, opt_encoding);
+}
diff --git a/javascript/subtle/ecdsa_sign_test.js b/javascript/subtle/ecdsa_sign_test.js
index 6ee9d29..7b20968 100644
--- a/javascript/subtle/ecdsa_sign_test.js
+++ b/javascript/subtle/ecdsa_sign_test.js
@@ -15,9 +15,9 @@
 goog.module('tink.subtle.EcdsaSignTest');
 goog.setTestOnly('tink.subtle.EcdsaSignTest');
 
-const EcdsaSign = goog.require('tink.subtle.EcdsaSign');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Random = goog.require('tink.subtle.Random');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const {fromJsonWebKey} = goog.require('google3.third_party.tink.javascript.subtle.ecdsa_sign');
 
 describe('ecdsa sign test', function() {
   beforeEach(function() {
@@ -32,7 +32,7 @@
 
   it('sign', async function() {
     const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
-    const signer = await EcdsaSign.newInstance(
+    const signer = await fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-256');
     for (let i = 0; i < 100; i++) {
       const data = Random.randBytes(i);
@@ -51,7 +51,7 @@
 
   it('sign with der encoding', async function() {
     const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
-    const signer = await EcdsaSign.newInstance(
+    const signer = await fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-256',
         EllipticCurves.EcdsaSignatureEncodingType.DER);
     for (let i = 0; i < 100; i++) {
@@ -83,7 +83,7 @@
 
   it('sign always generate new signatures', async function() {
     const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
-    const signer = await EcdsaSign.newInstance(
+    const signer = await fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-256');
     const signatures = new Set();
     for (let i = 0; i < 100; i++) {
@@ -97,7 +97,7 @@
   it('constructor with invalid hash', async function() {
     try {
       const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
-      await EcdsaSign.newInstance(
+      await fromJsonWebKey(
           await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-1');
       fail('Should throw an exception.');
     } catch (e) {
@@ -109,7 +109,7 @@
 
     try {
       const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-384');
-      await EcdsaSign.newInstance(
+      await fromJsonWebKey(
           await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-256');
       fail('Should throw an exception.');
     } catch (e) {
@@ -120,7 +120,7 @@
 
     try {
       const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-521');
-      await EcdsaSign.newInstance(
+      await fromJsonWebKey(
           await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-256');
       fail('Should throw an exception.');
     } catch (e) {
@@ -135,7 +135,7 @@
       const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
       const jwk = await EllipticCurves.exportCryptoKey(keyPair.privateKey);
       jwk.crv = 'blah';
-      await EcdsaSign.newInstance(jwk, 'SHA-256');
+      await fromJsonWebKey(jwk, 'SHA-256');
       fail('Should throw an exception.');
     } catch (e) {
       expect(e.toString()).toBe('SecurityException: unsupported curve: blah');
diff --git a/javascript/subtle/ecdsa_verify.js b/javascript/subtle/ecdsa_verify.js
deleted file mode 100644
index 926703d..0000000
--- a/javascript/subtle/ecdsa_verify.js
+++ /dev/null
@@ -1,95 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.EcdsaVerify');
-
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const {PublicKeyVerify} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_verify');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
-
-/**
- * Implementation of ECDSA verifying.
- *
- * @public
- * @final
- */
-class EcdsaVerify extends PublicKeyVerify {
-  /**
-   * @param {!webCrypto.CryptoKey} key
-   * @param {string} hash
-   * @param {!EllipticCurves.EcdsaSignatureEncodingType} encoding The
-   *     encoding of the signature.
-   */
-  constructor(key, hash, encoding) {
-    super();
-
-    /** @const @private {!webCrypto.CryptoKey} */
-    this.key_ = key;
-
-    /** @const @private {string} */
-    this.hash_ = hash;
-
-    /** @const @private {!EllipticCurves.EcdsaSignatureEncodingType} */
-    this.encoding_ = encoding;
-
-    /** @const @private {number} */
-    this.ieeeSignatureLength_ = 2 *
-        EllipticCurves.fieldSizeInBytes(
-            EllipticCurves.curveFromString(key.algorithm['namedCurve']));
-  }
-
-  /**
-   * @param {!webCrypto.JsonWebKey} jwk
-   * @param {string} hash
-   * @param {?EllipticCurves.EcdsaSignatureEncodingType=} opt_encoding The
-   *     optional encoding of the signature. If absent, default is IEEE P1363.
-   *
-   * @return {!Promise<!PublicKeyVerify>}
-   * @static
-   */
-  static async newInstance(jwk, hash, opt_encoding) {
-    if (!jwk) {
-      throw new SecurityException('public key has to be non-null');
-    }
-    Validators.validateEcdsaParams(jwk.crv, hash);
-    const cryptoKey = await EllipticCurves.importPublicKey('ECDSA', jwk);
-    if (!opt_encoding) {
-      opt_encoding = EllipticCurves.EcdsaSignatureEncodingType.IEEE_P1363;
-    }
-    return new EcdsaVerify(cryptoKey, hash, opt_encoding);
-  }
-
-  /**
-   * @override
-   */
-  async verify(signature, data) {
-    Validators.requireUint8Array(signature);
-    Validators.requireUint8Array(data);
-    if (this.encoding_ == EllipticCurves.EcdsaSignatureEncodingType.DER) {
-      signature =
-          EllipticCurves.ecdsaDer2Ieee(signature, this.ieeeSignatureLength_);
-    }
-    return await window.crypto.subtle.verify(
-        {
-          name: 'ECDSA',
-          hash: {
-            name: this.hash_,
-          },
-        },
-        this.key_, signature, data);
-  }
-}
-
-exports = EcdsaVerify;
diff --git a/javascript/subtle/ecdsa_verify.ts b/javascript/subtle/ecdsa_verify.ts
new file mode 100644
index 0000000..08b9803
--- /dev/null
+++ b/javascript/subtle/ecdsa_verify.ts
@@ -0,0 +1,75 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {SecurityException} from '../exception/security_exception';
+import {PublicKeyVerify} from '../signature/internal/public_key_verify';
+
+import * as EllipticCurves from './elliptic_curves';
+import * as Validators from './validators';
+
+/**
+ * Implementation of ECDSA verifying.
+ *
+ * @final
+ */
+export class EcdsaVerify implements PublicKeyVerify {
+  private readonly ieeeSignatureLength_: number;
+
+  /**
+   * @param encoding The
+   *     encoding of the signature.
+   */
+  constructor(
+      private readonly key: CryptoKey, private readonly hash: string,
+      private readonly encoding: EllipticCurves.EcdsaSignatureEncodingType) {
+    const {namedCurve}: Partial<EcKeyAlgorithm> = key.algorithm;
+    if (!namedCurve) {
+      throw new SecurityException('Curve has to be defined.');
+    }
+    this.ieeeSignatureLength_ = 2 *
+        EllipticCurves.fieldSizeInBytes(
+            EllipticCurves.curveFromString(namedCurve));
+  }
+
+  /**
+   * @override
+   */
+  async verify(signature: Uint8Array, message: Uint8Array): Promise<boolean> {
+    Validators.requireUint8Array(signature);
+    Validators.requireUint8Array(message);
+    if (this.encoding === EllipticCurves.EcdsaSignatureEncodingType.DER) {
+      signature =
+          EllipticCurves.ecdsaDer2Ieee(signature, this.ieeeSignatureLength_);
+    }
+    return window.crypto.subtle.verify(
+        {name: 'ECDSA', hash: {name: this.hash}}, this.key, signature, message);
+  }
+}
+
+/**
+ * @param opt_encoding The
+ *     optional encoding of the signature. If absent, default is IEEE P1363.
+ */
+export async function fromJsonWebKey(
+    jwk: JsonWebKey, hash: string,
+    encoding: EllipticCurves.EcdsaSignatureEncodingType =
+        EllipticCurves.EcdsaSignatureEncodingType.IEEE_P1363):
+    Promise<PublicKeyVerify> {
+  if (!jwk) {
+    throw new SecurityException('public key has to be non-null');
+  }
+  const {crv} = jwk;
+  if (!crv) {
+    throw new SecurityException('curve has to be defined');
+  }
+  Validators.validateEcdsaParams(crv, hash);
+  const cryptoKey = await EllipticCurves.importPublicKey('ECDSA', jwk);
+  return new EcdsaVerify(cryptoKey, hash, encoding);
+}
diff --git a/javascript/subtle/ecdsa_verify_test.js b/javascript/subtle/ecdsa_verify_test.js
index c8ed446..e3220cc 100644
--- a/javascript/subtle/ecdsa_verify_test.js
+++ b/javascript/subtle/ecdsa_verify_test.js
@@ -15,14 +15,14 @@
 goog.module('tink.subtle.EcdsaVerifyTest');
 goog.setTestOnly('tink.subtle.EcdsaVerifyTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const EcdsaSign = goog.require('tink.subtle.EcdsaSign');
-const EcdsaVerify = goog.require('tink.subtle.EcdsaVerify');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const {PublicKeyVerify} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_verify');
-const Random = goog.require('tink.subtle.Random');
-const Validators = goog.require('tink.subtle.Validators');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Validators = goog.require('google3.third_party.tink.javascript.subtle.validators');
 const wycheproofEcdsaTestVectors = goog.require('tink.subtle.wycheproofEcdsaTestVectors');
+const ecdsaSign = goog.require('google3.third_party.tink.javascript.subtle.ecdsa_sign');
+const ecdsaVerify = goog.require('google3.third_party.tink.javascript.subtle.ecdsa_verify');
+const {PublicKeyVerify} = goog.require('google3.third_party.tink.javascript.signature.internal.public_key_verify');
 
 describe('ecdsa verify test', function() {
   beforeEach(function() {
@@ -37,9 +37,9 @@
 
   it('verify', async function() {
     const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
-    const signer = await EcdsaSign.newInstance(
+    const signer = await ecdsaSign.fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-256');
-    const verifier = await EcdsaVerify.newInstance(
+    const verifier = await ecdsaVerify.fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.publicKey), 'SHA-256');
     for (let i = 0; i < 100; i++) {
       const data = Random.randBytes(i);
@@ -50,12 +50,12 @@
 
   it('verify with der encoding', async function() {
     const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
-    const signer = await EcdsaSign.newInstance(
+    const signer = await ecdsaSign.fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-256',
         EllipticCurves.EcdsaSignatureEncodingType.DER);
-    const verifier = await EcdsaVerify.newInstance(
+    const verifier = await ecdsaVerify.fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.publicKey), 'SHA-256');
-    const verifierDer = await EcdsaVerify.newInstance(
+    const verifierDer = await ecdsaVerify.fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.publicKey), 'SHA-256',
         EllipticCurves.EcdsaSignatureEncodingType.DER);
     for (let i = 0; i < 100; i++) {
@@ -69,7 +69,7 @@
   it('constructor with invalid hash', async function() {
     try {
       const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
-      await EcdsaVerify.newInstance(
+      await ecdsaVerify.fromJsonWebKey(
           await EllipticCurves.exportCryptoKey(keyPair.publicKey), 'SHA-1');
       fail('Should throw an exception.');
     } catch (e) {
@@ -80,7 +80,7 @@
 
     try {
       const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-384');
-      await EcdsaVerify.newInstance(
+      await ecdsaVerify.fromJsonWebKey(
           await EllipticCurves.exportCryptoKey(keyPair.publicKey), 'SHA-256');
       fail('Should throw an exception.');
     } catch (e) {
@@ -91,7 +91,7 @@
 
     try {
       const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-521');
-      await EcdsaVerify.newInstance(
+      await ecdsaVerify.fromJsonWebKey(
           await EllipticCurves.exportCryptoKey(keyPair.publicKey), 'SHA-256');
       fail('Should throw an exception.');
     } catch (e) {
@@ -106,7 +106,7 @@
       const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
       const jwk = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
       jwk.crv = 'blah';
-      await EcdsaVerify.newInstance(jwk, 'SHA-256');
+      await ecdsaVerify.fromJsonWebKey(jwk, 'SHA-256');
       fail('Should throw an exception.');
     } catch (e) {
       expect(e.toString()).toBe('SecurityException: unsupported curve: blah');
@@ -115,9 +115,9 @@
 
   it('verify modified signature', async function() {
     const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
-    const signer = await EcdsaSign.newInstance(
+    const signer = await ecdsaSign.fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-256');
-    const verifier = await EcdsaVerify.newInstance(
+    const verifier = await ecdsaVerify.fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.publicKey), 'SHA-256');
     const data = Random.randBytes(20);
     const signature = await signer.sign(data);
@@ -133,9 +133,9 @@
 
   it('verify modified data', async function() {
     const keyPair = await EllipticCurves.generateKeyPair('ECDSA', 'P-256');
-    const signer = await EcdsaSign.newInstance(
+    const signer = await ecdsaSign.fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.privateKey), 'SHA-256');
-    const verifier = await EcdsaVerify.newInstance(
+    const verifier = await ecdsaVerify.fromJsonWebKey(
         await EllipticCurves.exportCryptoKey(keyPair.publicKey), 'SHA-256');
     const data = Random.randBytes(20);
     const signature = await signer.sign(data);
@@ -159,7 +159,7 @@
         continue;
       }
       const verifier =
-          await EcdsaVerify.newInstance(testGroup['jwk'], testGroup['sha']);
+          await ecdsaVerify.fromJsonWebKey(testGroup['jwk'], testGroup['sha']);
       let errors = '';
       for (let test of testGroup['tests']) {
         errors += await runWycheproofTest(verifier, test);
diff --git a/javascript/subtle/ecies_aead_hkdf_dem_helper.js b/javascript/subtle/ecies_aead_hkdf_dem_helper.ts
similarity index 66%
rename from javascript/subtle/ecies_aead_hkdf_dem_helper.js
rename to javascript/subtle/ecies_aead_hkdf_dem_helper.ts
index d6bb7f7..4aacb40 100644
--- a/javascript/subtle/ecies_aead_hkdf_dem_helper.js
+++ b/javascript/subtle/ecies_aead_hkdf_dem_helper.ts
@@ -1,39 +1,30 @@
 // 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.
-//
 ////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.EciesAeadHkdfDemHelper');
-
-const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
+import {Aead} from '../aead/internal/aead';
 
 /**
  * A helper for DEM (data encapsulation mechanism) of ECIES-AEAD-HKDF.
- * @record
  */
-class EciesAeadHkdfDemHelper {
+export interface EciesAeadHkdfDemHelper {
   /**
-   * @return {number} the size of the DEM key in bytes
+   * @return the size of the DEM key in bytes
    */
-  getDemKeySizeInBytes() {}
+  getDemKeySizeInBytes(): number;
 
   /**
    * Creates a new `Aead` primitive that uses the key material given in
    * `demKey`, which must be of length `getDemKeySizeInBytes()`.
    *
-   * @param {!Uint8Array} demKey the DEM key.
-   * @return {!Promise.<!Aead>} the newly created `Aead` primitive.
+   * @param demKey the DEM key.
+   * @return the newly created `Aead` primitive.
    */
-  getAead(demKey) {}
+  getAead(demKey: Uint8Array): Promise<Aead>;
 }
-
-exports = EciesAeadHkdfDemHelper;
diff --git a/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt.js b/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt.js
deleted file mode 100644
index 7f4e04f..0000000
--- a/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt.js
+++ /dev/null
@@ -1,158 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.EciesAeadHkdfHybridDecrypt');
-
-const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
-const EciesAeadHkdfDemHelper = goog.require('tink.subtle.EciesAeadHkdfDemHelper');
-const EciesHkdfKemRecipient = goog.require('tink.subtle.EciesHkdfKemRecipient');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const {HybridDecrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_decrypt');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-
-/**
- * Implementation of ECIES AEAD HKDF hybrid decryption.
- *
- * @protected
- * @final
- */
-class EciesAeadHkdfHybridDecrypt extends HybridDecrypt {
-  /**
-   * @param {!webCrypto.JsonWebKey} recipientPrivateKey
-   * @param {!EciesHkdfKemRecipient} kemRecipient
-   * @param {string} hkdfHash the name of the HMAC algorithm, accepted names
-   *     are: SHA-1, SHA-256 and SHA-512.
-   * @param {!EllipticCurves.PointFormatType} pointFormat
-   * @param {!EciesAeadHkdfDemHelper} demHelper
-   * @param {!Uint8Array=} opt_hkdfSalt
-   */
-  constructor(
-      recipientPrivateKey, kemRecipient, hkdfHash, pointFormat, demHelper,
-      opt_hkdfSalt) {
-    super();
-
-    if (!recipientPrivateKey) {
-      throw new SecurityException('Recipient private key has to be non-null.');
-    }
-    if (!kemRecipient) {
-      throw new SecurityException('KEM recipient has to be non-null.');
-    }
-    if (!hkdfHash) {
-      throw new SecurityException('HKDF hash algorithm has to be non-null.');
-    }
-    if (!pointFormat) {
-      throw new SecurityException('Point format has to be non-null.');
-    }
-    if (!demHelper) {
-      throw new SecurityException('DEM helper has to be non-null.');
-    }
-
-    const curveType =
-        EllipticCurves.curveFromString(recipientPrivateKey['crv']);
-    const headerSize =
-        EllipticCurves.encodingSizeInBytes(curveType, pointFormat);
-
-    /** @private @const {!EciesHkdfKemRecipient} */
-    this.kemRecipient_ = kemRecipient;
-    /** @private @const {string} */
-    this.hkdfHash_ = hkdfHash;
-    /** @private @const {!EllipticCurves.PointFormatType} */
-    this.pointFormat_ = pointFormat;
-    /** @private @const {!EciesAeadHkdfDemHelper} */
-    this.demHelper_ = demHelper;
-    /** @private @const {number} */
-    this.headerSize_ = headerSize;
-    /** @private @const {!Uint8Array|undefined} */
-    this.hkdfSalt_ = opt_hkdfSalt;
-  }
-
-  /**
-   * @param {!webCrypto.JsonWebKey} recipientPrivateKey
-   * @param {string} hkdfHash the name of the HMAC algorithm, accepted names
-   *     are: SHA-1, SHA-256 and SHA-512.
-   * @param {!EllipticCurves.PointFormatType} pointFormat
-   * @param {!EciesAeadHkdfDemHelper} demHelper
-   * @param {!Uint8Array=} opt_hkdfSalt
-   *
-   * @return {!Promise.<!HybridDecrypt>}
-   */
-  static async newInstance(
-      recipientPrivateKey, hkdfHash, pointFormat, demHelper, opt_hkdfSalt) {
-    if (!recipientPrivateKey) {
-      throw new SecurityException('Recipient private key has to be non-null.');
-    }
-    if (!hkdfHash) {
-      throw new SecurityException('HKDF hash algorithm has to be non-null.');
-    }
-    if (!pointFormat) {
-      throw new SecurityException('Point format has to be non-null.');
-    }
-    if (!demHelper) {
-      throw new SecurityException('DEM helper has to be non-null.');
-    }
-
-    if (!recipientPrivateKey) {
-      throw new SecurityException('Recipient private key has to be non-null.');
-    }
-    const kemRecipient =
-        await EciesHkdfKemRecipient.newInstance(recipientPrivateKey);
-
-    return new EciesAeadHkdfHybridDecrypt(
-        recipientPrivateKey, kemRecipient, hkdfHash, pointFormat, demHelper,
-        opt_hkdfSalt);
-  }
-
-  /**
-   * Decrypts ciphertext using opt_contextInfo as info parameter of the
-   * underlying HKDF.
-   *
-   * @override
-   */
-  async decrypt(ciphertext, opt_contextInfo) {
-    if (ciphertext.length < this.headerSize_) {
-      throw new SecurityException('Ciphertext is too short.');
-    }
-
-    // Split the ciphertext to KEM token and AEAD ciphertext.
-    const kemToken = ciphertext.slice(0, this.headerSize_);
-    const ciphertextBody =
-        ciphertext.slice(this.headerSize_, ciphertext.length);
-
-    const aead = await this.getAead_(kemToken, opt_contextInfo);
-    return await aead.decrypt(ciphertextBody);
-  }
-
-  /**
-   * @private
-   * @param {!Uint8Array} kemToken
-   * @param {?Uint8Array=} opt_contextInfo
-   * @return {!Promise<!Aead>}
-   */
-  async getAead_(kemToken, opt_contextInfo) {
-    // Variable hkdfInfo is not optional for decapsulate method. Thus it should
-    // be an empty array in case that it is not defined by the caller of decrypt
-    // method.
-    if (!opt_contextInfo) {
-      opt_contextInfo = new Uint8Array(0);
-    }
-
-    const symmetricKey = await this.kemRecipient_.decapsulate(
-        kemToken, this.demHelper_.getDemKeySizeInBytes(), this.pointFormat_,
-        this.hkdfHash_, opt_contextInfo, this.hkdfSalt_);
-    return await this.demHelper_.getAead(symmetricKey);
-  }
-}
-
-exports = EciesAeadHkdfHybridDecrypt;
diff --git a/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt.ts b/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt.ts
new file mode 100644
index 0000000..45b0015
--- /dev/null
+++ b/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt.ts
@@ -0,0 +1,132 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {Aead} from '../aead/internal/aead';
+import {SecurityException} from '../exception/security_exception';
+import {HybridDecrypt} from '../hybrid/internal/hybrid_decrypt';
+
+import {EciesAeadHkdfDemHelper} from './ecies_aead_hkdf_dem_helper';
+import {EciesHkdfKemRecipient, fromJsonWebKey as kemRecipientFromJsonWebKey} from './ecies_hkdf_kem_recipient';
+import * as EllipticCurves from './elliptic_curves';
+
+/**
+ * Implementation of ECIES AEAD HKDF hybrid decryption.
+ *
+ * @final
+ */
+export class EciesAeadHkdfHybridDecrypt implements HybridDecrypt {
+  private readonly kemRecipient_: EciesHkdfKemRecipient;
+  private readonly hkdfHash_: string;
+  private readonly pointFormat_: EllipticCurves.PointFormatType;
+  private readonly demHelper_: EciesAeadHkdfDemHelper;
+  private readonly headerSize_: number;
+  private readonly hkdfSalt_: Uint8Array|undefined;
+
+  /**
+   * @param hkdfHash the name of the HMAC algorithm, accepted names
+   *     are: SHA-1, SHA-256 and SHA-512.
+   */
+  constructor(
+      recipientPrivateKey: JsonWebKey, kemRecipient: EciesHkdfKemRecipient,
+      hkdfHash: string, pointFormat: EllipticCurves.PointFormatType,
+      demHelper: EciesAeadHkdfDemHelper, opt_hkdfSalt?: Uint8Array) {
+    if (!recipientPrivateKey) {
+      throw new SecurityException('Recipient private key has to be non-null.');
+    }
+    if (!kemRecipient) {
+      throw new SecurityException('KEM recipient has to be non-null.');
+    }
+    if (!hkdfHash) {
+      throw new SecurityException('HKDF hash algorithm has to be non-null.');
+    }
+    if (!pointFormat) {
+      throw new SecurityException('Point format has to be non-null.');
+    }
+    if (!demHelper) {
+      throw new SecurityException('DEM helper has to be non-null.');
+    }
+    const {crv} = recipientPrivateKey;
+    if (!crv) {
+      throw new SecurityException('Curve has to be defined.');
+    }
+    const curveType = EllipticCurves.curveFromString(crv);
+    const headerSize =
+        EllipticCurves.encodingSizeInBytes(curveType, pointFormat);
+    this.kemRecipient_ = kemRecipient;
+    this.hkdfHash_ = hkdfHash;
+    this.pointFormat_ = pointFormat;
+    this.demHelper_ = demHelper;
+    this.headerSize_ = headerSize;
+    this.hkdfSalt_ = opt_hkdfSalt;
+  }
+
+  /**
+   * Decrypts ciphertext using opt_contextInfo as info parameter of the
+   * underlying HKDF.
+   *
+   * @override
+   */
+  async decrypt(ciphertext: Uint8Array, associatedData?: Uint8Array) {
+    if (ciphertext.length < this.headerSize_) {
+      throw new SecurityException('Ciphertext is too short.');
+    }
+
+    // Split the ciphertext to KEM token and AEAD ciphertext.
+    const kemToken = ciphertext.slice(0, this.headerSize_);
+    const ciphertextBody =
+        ciphertext.slice(this.headerSize_, ciphertext.length);
+    const aead = await this.getAead_(kemToken, associatedData);
+    return aead.decrypt(ciphertextBody);
+  }
+
+  private async getAead_(
+      kemToken: Uint8Array, opt_contextInfo?: Uint8Array|null): Promise<Aead> {
+    // Variable hkdfInfo is not optional for decapsulate method. Thus it should
+    // be an empty array in case that it is not defined by the caller of decrypt
+    // method.
+    if (!opt_contextInfo) {
+      opt_contextInfo = new Uint8Array(0);
+    }
+    const symmetricKey = await this.kemRecipient_.decapsulate(
+        kemToken, this.demHelper_.getDemKeySizeInBytes(), this.pointFormat_,
+        this.hkdfHash_, opt_contextInfo, this.hkdfSalt_);
+    return this.demHelper_.getAead(symmetricKey);
+  }
+}
+
+/**
+ * @param hkdfHash the name of the HMAC algorithm, accepted names
+ *     are: SHA-1, SHA-256 and SHA-512.
+ */
+export async function fromJsonWebKey(
+    recipientPrivateKey: JsonWebKey, hkdfHash: string,
+    pointFormat: EllipticCurves.PointFormatType,
+    demHelper: EciesAeadHkdfDemHelper,
+    opt_hkdfSalt?: Uint8Array): Promise<HybridDecrypt> {
+  if (!recipientPrivateKey) {
+    throw new SecurityException('Recipient private key has to be non-null.');
+  }
+  if (!hkdfHash) {
+    throw new SecurityException('HKDF hash algorithm has to be non-null.');
+  }
+  if (!pointFormat) {
+    throw new SecurityException('Point format has to be non-null.');
+  }
+  if (!demHelper) {
+    throw new SecurityException('DEM helper has to be non-null.');
+  }
+  if (!recipientPrivateKey) {
+    throw new SecurityException('Recipient private key has to be non-null.');
+  }
+  const kemRecipient = await kemRecipientFromJsonWebKey(recipientPrivateKey);
+  return new EciesAeadHkdfHybridDecrypt(
+      recipientPrivateKey, kemRecipient, hkdfHash, pointFormat, demHelper,
+      opt_hkdfSalt);
+}
diff --git a/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt_test.js b/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt_test.js
index 8af957d..d9c8b53 100644
--- a/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt_test.js
+++ b/javascript/subtle/ecies_aead_hkdf_hybrid_decrypt_test.js
@@ -18,11 +18,11 @@
 const AeadConfig = goog.require('tink.aead.AeadConfig');
 const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
 const DemHelper = goog.require('tink.hybrid.RegistryEciesAeadHkdfDemHelper');
-const EciesAeadHkdfHybridDecrypt = goog.require('tink.subtle.EciesAeadHkdfHybridDecrypt');
-const EciesAeadHkdfHybridEncrypt = goog.require('tink.subtle.EciesAeadHkdfHybridEncrypt');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
+const {fromJsonWebKey: decrypterFromJsonWebKey} = goog.require('google3.third_party.tink.javascript.subtle.ecies_aead_hkdf_hybrid_decrypt');
+const {fromJsonWebKey: encrypterFromJsonWebKey} = goog.require('google3.third_party.tink.javascript.subtle.ecies_aead_hkdf_hybrid_encrypt');
 
 describe('ecies aead hkdf hybrid decrypt test', function() {
   beforeEach(function() {
@@ -45,7 +45,7 @@
     const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
     const demHelper = new DemHelper(AeadKeyTemplates.aes128CtrHmacSha256());
 
-    await EciesAeadHkdfHybridDecrypt.newInstance(
+    await decrypterFromJsonWebKey(
         privateKey, hkdfHash, pointFormat, demHelper, hkdfSalt);
   });
 
@@ -63,9 +63,9 @@
     const privateKey = await EllipticCurves.exportCryptoKey(keyPair.privateKey);
     const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
 
-    const hybridEncrypt = await EciesAeadHkdfHybridEncrypt.newInstance(
+    const hybridEncrypt = await encrypterFromJsonWebKey(
         publicKey, hkdfHash, pointFormat, demHelper);
-    const hybridDecrypt = await EciesAeadHkdfHybridDecrypt.newInstance(
+    const hybridDecrypt = await decrypterFromJsonWebKey(
         privateKey, hkdfHash, pointFormat, demHelper);
 
     const plaintext = Random.randBytes(10);
@@ -90,11 +90,11 @@
        const keyTemplate = AeadKeyTemplates.aes256CtrHmacSha256();
 
        const demHelperEncrypt = new DemHelper(keyTemplate);
-       const hybridEncrypt = await EciesAeadHkdfHybridEncrypt.newInstance(
+       const hybridEncrypt = await encrypterFromJsonWebKey(
            publicKey, hkdfHash, pointFormat, demHelperEncrypt);
 
        const demHelperDecrypt = new DemHelper(keyTemplate);
-       const hybridDecrypt = await EciesAeadHkdfHybridDecrypt.newInstance(
+       const hybridDecrypt = await decrypterFromJsonWebKey(
            privateKey, hkdfHash, pointFormat, demHelperDecrypt);
 
        const plaintext = Random.randBytes(15);
@@ -111,23 +111,25 @@
     const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
     const hmacAlgorithms = ['SHA-1', 'SHA-256', 'SHA-512'];
     const demHelper = new DemHelper(AeadKeyTemplates.aes256CtrHmacSha256());
-    const curves = Object.keys(EllipticCurves.CurveType);
+    const curves = [
+      EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+      EllipticCurves.CurveType.P521
+    ];
 
     // Test the encryption for different HMAC algorithms and different types of
     // curves.
     for (let hkdfHash of hmacAlgorithms) {
       for (let curve of curves) {
-        const curveName =
-            EllipticCurves.curveToString(EllipticCurves.CurveType[curve]);
+        const curveName = EllipticCurves.curveToString(curve);
         const keyPair = await EllipticCurves.generateKeyPair('ECDH', curveName);
         const privateKey =
             await EllipticCurves.exportCryptoKey(keyPair.privateKey);
         const publicKey =
             await EllipticCurves.exportCryptoKey(keyPair.publicKey);
 
-        const hybridEncrypt = await EciesAeadHkdfHybridEncrypt.newInstance(
+        const hybridEncrypt = await encrypterFromJsonWebKey(
             publicKey, hkdfHash, pointFormat, demHelper, hkdfSalt);
-        const hybridDecrypt = await EciesAeadHkdfHybridDecrypt.newInstance(
+        const hybridDecrypt = await decrypterFromJsonWebKey(
             privateKey, hkdfHash, pointFormat, demHelper, hkdfSalt);
 
         for (let i = 0; i < repetitions; ++i) {
diff --git a/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt.js b/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt.js
deleted file mode 100644
index 23a5a4a..0000000
--- a/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt.js
+++ /dev/null
@@ -1,126 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.EciesAeadHkdfHybridEncrypt');
-
-const Bytes = goog.require('tink.subtle.Bytes');
-const EciesAeadHkdfDemHelper = goog.require('tink.subtle.EciesAeadHkdfDemHelper');
-const EciesHkdfKemSender = goog.require('tink.subtle.EciesHkdfKemSender');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const {HybridEncrypt} = goog.require('google3.third_party.tink.javascript.hybrid.internal.hybrid_encrypt');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-
-/**
- * Implementation of ECIES AEAD HKDF hybrid encryption.
- *
- * @protected
- * @final
- */
-class EciesAeadHkdfHybridEncrypt extends HybridEncrypt {
-  /**
-   * @param {!EciesHkdfKemSender} kemSender
-   * @param {string} hkdfHash the name of the HMAC algorithm, accepted names
-   *     are: SHA-1, SHA-256 and SHA-512.
-   * @param {!EllipticCurves.PointFormatType} pointFormat
-   * @param {!EciesAeadHkdfDemHelper} demHelper
-   * @param {!Uint8Array=} opt_hkdfSalt
-   */
-  constructor(kemSender, hkdfHash, pointFormat, demHelper, opt_hkdfSalt) {
-    super();
-
-    // TODO(thaidn): do we actually need these null checks?
-    if (!kemSender) {
-      throw new SecurityException('KEM sender has to be non-null.');
-    }
-    if (!hkdfHash) {
-      throw new SecurityException('HMAC algorithm has to be non-null.');
-    }
-    if (!pointFormat) {
-      throw new SecurityException('Point format has to be non-null.');
-    }
-    if (!demHelper) {
-      throw new SecurityException('DEM helper has to be non-null.');
-    }
-
-    /** @private @const {!EciesHkdfKemSender} */
-    this.kemSender_ = kemSender;
-    /** @private @const {string} */
-    this.hkdfHash_ = hkdfHash;
-    /** @private @const {!EllipticCurves.PointFormatType} */
-    this.pointFormat_ = pointFormat;
-    /** @private @const {!EciesAeadHkdfDemHelper} */
-    this.demHelper_ = demHelper;
-    /** @private @const {!Uint8Array|undefined} */
-    this.hkdfSalt_ = opt_hkdfSalt;
-  }
-
-  /**
-   * @param {!webCrypto.JsonWebKey} recipientPublicKey
-   * @param {string} hkdfHash the name of the HMAC algorithm, accepted names
-   *     are: SHA-1, SHA-256 and SHA-512.
-   * @param {!EllipticCurves.PointFormatType} pointFormat
-   * @param {!EciesAeadHkdfDemHelper} demHelper
-   * @param {!Uint8Array=} opt_hkdfSalt
-   *
-   * @return {!Promise.<!HybridEncrypt>}
-   */
-  static async newInstance(
-      recipientPublicKey, hkdfHash, pointFormat, demHelper, opt_hkdfSalt) {
-    if (!recipientPublicKey) {
-      throw new SecurityException('Recipient public key has to be non-null.');
-    }
-    if (!hkdfHash) {
-      throw new SecurityException('HMAC algorithm has to be non-null.');
-    }
-    if (!pointFormat) {
-      throw new SecurityException('Point format has to be non-null.');
-    }
-    if (!demHelper) {
-      throw new SecurityException('DEM helper has to be non-null.');
-    }
-
-    const kemSender = await EciesHkdfKemSender.newInstance(recipientPublicKey);
-    return new EciesAeadHkdfHybridEncrypt(
-        kemSender, hkdfHash, pointFormat, demHelper, opt_hkdfSalt);
-  }
-
-  /**
-   * Encrypts plaintext using opt_contextInfo as info parameter of the
-   * underlying HKDF.
-   *
-   * @override
-   */
-  async encrypt(plaintext, opt_contextInfo) {
-    // Variable hkdfInfo is not optional for encapsulate method. Thus it
-    // should be an empty array in case that it is not defined by caller of this
-    // method.
-    if (!opt_contextInfo) {
-      opt_contextInfo = new Uint8Array(0);
-    }
-
-    const keySizeInBytes = this.demHelper_.getDemKeySizeInBytes();
-    const kemKey = await this.kemSender_.encapsulate(
-        keySizeInBytes, this.pointFormat_, this.hkdfHash_, opt_contextInfo,
-        this.hkdfSalt_);
-    const aead = await this.demHelper_.getAead(kemKey['key']);
-
-    const ciphertextBody = await aead.encrypt(plaintext);
-    const header = kemKey['token'];
-
-    return Bytes.concat(header, ciphertextBody);
-  }
-}
-
-exports = EciesAeadHkdfHybridEncrypt;
diff --git a/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt.ts b/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt.ts
new file mode 100644
index 0000000..ab90952
--- /dev/null
+++ b/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt.ts
@@ -0,0 +1,103 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {SecurityException} from '../exception/security_exception';
+import {HybridEncrypt} from '../hybrid/internal/hybrid_encrypt';
+
+import * as Bytes from './bytes';
+import {EciesAeadHkdfDemHelper} from './ecies_aead_hkdf_dem_helper';
+import * as sender from './ecies_hkdf_kem_sender';
+import * as EllipticCurves from './elliptic_curves';
+
+/**
+ * Implementation of ECIES AEAD HKDF hybrid encryption.
+ *
+ * @final
+ */
+export class EciesAeadHkdfHybridEncrypt implements HybridEncrypt {
+  private readonly kemSender_: sender.EciesHkdfKemSender;
+  private readonly hkdfHash_: string;
+  private readonly pointFormat_: EllipticCurves.PointFormatType;
+  private readonly demHelper_: EciesAeadHkdfDemHelper;
+  private readonly hkdfSalt_: Uint8Array|undefined;
+
+  /**
+   * @param hkdfHash the name of the HMAC algorithm, accepted names
+   *     are: SHA-1, SHA-256 and SHA-512.
+   */
+  constructor(
+      kemSender: sender.EciesHkdfKemSender, hkdfHash: string,
+      pointFormat: EllipticCurves.PointFormatType,
+      demHelper: EciesAeadHkdfDemHelper, opt_hkdfSalt?: Uint8Array) {
+    // TODO(thaidn): do we actually need these null checks?
+    if (!kemSender) {
+      throw new SecurityException('KEM sender has to be non-null.');
+    }
+    if (!hkdfHash) {
+      throw new SecurityException('HMAC algorithm has to be non-null.');
+    }
+    if (!pointFormat) {
+      throw new SecurityException('Point format has to be non-null.');
+    }
+    if (!demHelper) {
+      throw new SecurityException('DEM helper has to be non-null.');
+    }
+    this.kemSender_ = kemSender;
+    this.hkdfHash_ = hkdfHash;
+    this.pointFormat_ = pointFormat;
+    this.demHelper_ = demHelper;
+    this.hkdfSalt_ = opt_hkdfSalt;
+  }
+
+  /**
+   * Encrypts plaintext using opt_contextInfo as info parameter of the
+   * underlying HKDF.
+   *
+   * @override
+   */
+  async encrypt(
+      plaintext: Uint8Array,
+      associatedData: Uint8Array = new Uint8Array(0)): Promise<Uint8Array> {
+    const keySizeInBytes = this.demHelper_.getDemKeySizeInBytes();
+    const kemKey = await this.kemSender_.encapsulate(
+        keySizeInBytes, this.pointFormat_, this.hkdfHash_, associatedData,
+        this.hkdfSalt_);
+    const aead = await this.demHelper_.getAead(kemKey['key']);
+    const ciphertextBody = await aead.encrypt(plaintext);
+    const header = kemKey['token'];
+    return Bytes.concat(header, ciphertextBody);
+  }
+}
+
+/**
+ * @param hkdfHash the name of the HMAC algorithm, accepted names
+ *     are: SHA-1, SHA-256 and SHA-512.
+ */
+export async function fromJsonWebKey(
+    recipientPublicKey: JsonWebKey, hkdfHash: string,
+    pointFormat: EllipticCurves.PointFormatType,
+    demHelper: EciesAeadHkdfDemHelper,
+    opt_hkdfSalt?: Uint8Array): Promise<HybridEncrypt> {
+  if (!recipientPublicKey) {
+    throw new SecurityException('Recipient public key has to be non-null.');
+  }
+  if (!hkdfHash) {
+    throw new SecurityException('HMAC algorithm has to be non-null.');
+  }
+  if (!pointFormat) {
+    throw new SecurityException('Point format has to be non-null.');
+  }
+  if (!demHelper) {
+    throw new SecurityException('DEM helper has to be non-null.');
+  }
+  const kemSender = await sender.fromJsonWebKey(recipientPublicKey);
+  return new EciesAeadHkdfHybridEncrypt(
+      kemSender, hkdfHash, pointFormat, demHelper, opt_hkdfSalt);
+}
diff --git a/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt_test.js b/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt_test.js
index 42a7538..4d2242f 100644
--- a/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt_test.js
+++ b/javascript/subtle/ecies_aead_hkdf_hybrid_encrypt_test.js
@@ -17,11 +17,11 @@
 
 const AeadConfig = goog.require('tink.aead.AeadConfig');
 const AeadKeyTemplates = goog.require('tink.aead.AeadKeyTemplates');
-const EciesAeadHkdfHybridEncrypt = goog.require('tink.subtle.EciesAeadHkdfHybridEncrypt');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Random = goog.require('tink.subtle.Random');
-const Registry = goog.require('tink.Registry');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const Registry = goog.require('google3.third_party.tink.javascript.internal.registry');
 const RegistryEciesAeadHkdfDemHelper = goog.require('tink.hybrid.RegistryEciesAeadHkdfDemHelper');
+const {fromJsonWebKey} = goog.require('google3.third_party.tink.javascript.subtle.ecies_aead_hkdf_hybrid_encrypt');
 
 describe('ecies aead hkdf hybrid encrypt test', function() {
   beforeEach(function() {
@@ -44,8 +44,7 @@
     const demHelper = new RegistryEciesAeadHkdfDemHelper(
         AeadKeyTemplates.aes128CtrHmacSha256());
 
-    await EciesAeadHkdfHybridEncrypt.newInstance(
-        publicKey, hkdfHash, pointFormat, demHelper);
+    await fromJsonWebKey(publicKey, hkdfHash, pointFormat, demHelper);
   });
 
   it('encrypt, different arguments', async function() {
@@ -58,14 +57,15 @@
     // Test the encryption for different HMAC algorithms and different types of
     // curves.
     for (let hkdfHash of hmacAlgorithms) {
-      for (let curve of Object.keys(EllipticCurves.CurveType)) {
-        const curveName =
-            EllipticCurves.curveToString(EllipticCurves.CurveType[curve]);
+      for (let curve
+               of [EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+                   EllipticCurves.CurveType.P521]) {
+        const curveName = EllipticCurves.curveToString(curve);
         const keyPair = await EllipticCurves.generateKeyPair('ECDH', curveName);
         const publicKey =
             await EllipticCurves.exportCryptoKey(keyPair.publicKey);
 
-        const hybridEncrypt = await EciesAeadHkdfHybridEncrypt.newInstance(
+        const hybridEncrypt = await fromJsonWebKey(
             publicKey, hkdfHash, pointFormat, demHelper, hkdfSalt);
 
         const plaintext = Random.randBytes(15);
diff --git a/javascript/subtle/ecies_hkdf_kem_recipient.js b/javascript/subtle/ecies_hkdf_kem_recipient.js
deleted file mode 100644
index f9f3f97..0000000
--- a/javascript/subtle/ecies_hkdf_kem_recipient.js
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.EciesHkdfKemRecipient');
-
-const Bytes = goog.require('tink.subtle.Bytes');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Hkdf = goog.require('tink.subtle.Hkdf');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-
-/**
- * HKDF-based ECIES-KEM (key encapsulation mechanism) for ECIES recipient.
- */
-class EciesHkdfKemRecipient {
-  /**
-   * @param {!webCrypto.CryptoKey} privateKey
-   */
-  constructor(privateKey) {
-    if (!privateKey) {
-      throw new SecurityException('Private key has to be non-null.');
-    }
-    // CryptoKey should have the properties type and algorithm.
-    if (privateKey.type !== 'private' || !privateKey.algorithm) {
-      throw new SecurityException('Expected crypto key of type: private.');
-    }
-    /** @const @private {!webCrypto.CryptoKey} */
-    this.privateKey_ = privateKey;
-  }
-
-  /**
-   * @param {!webCrypto.JsonWebKey} jwk
-   * @return {!Promise.<!EciesHkdfKemRecipient>}
-   * @static
-   */
-  static async newInstance(jwk) {
-    const privateKey = await EllipticCurves.importPrivateKey('ECDH', jwk);
-    return new EciesHkdfKemRecipient(privateKey);
-  }
-
-  /**
-   * @param {!Uint8Array} kemToken the public ephemeral point.
-   * @param {number} keySizeInBytes The length of the generated pseudorandom
-   *     string in bytes. The maximal size is 255 * DigestSize, where DigestSize
-   *     is the size of the underlying HMAC.
-   * @param {!EllipticCurves.PointFormatType} pointFormat The format of the
-   *     public ephemeral point.
-   * @param {string} hkdfHash the name of the hash function. Accepted names are
-   *     SHA-1, SHA-256 and SHA-512.
-   * @param {!Uint8Array} hkdfInfo Context and application specific
-   *     information (can be a zero-length array).
-   * @param {!Uint8Array=} opt_hkdfSalt Salt value (a non-secret random
-   *     value). If not provided, it is set to a string of hash length zeros.
-   * @return {!Promise.<!Uint8Array>} The KEM key and token.
-   */
-  async decapsulate(
-      kemToken, keySizeInBytes, pointFormat, hkdfHash, hkdfInfo, opt_hkdfSalt) {
-    const jwk = EllipticCurves.pointDecode(
-        this.privateKey_.algorithm['namedCurve'], pointFormat, kemToken);
-    const publicKey = await EllipticCurves.importPublicKey('ECDH', jwk);
-    const sharedSecret = await EllipticCurves.computeEcdhSharedSecret(
-        this.privateKey_, publicKey);
-    const hkdfIkm = Bytes.concat(kemToken, sharedSecret);
-    const kemKey = await Hkdf.compute(
-        keySizeInBytes, hkdfHash, hkdfIkm, hkdfInfo, opt_hkdfSalt);
-    return kemKey;
-  }
-}
-
-exports = EciesHkdfKemRecipient;
diff --git a/javascript/subtle/ecies_hkdf_kem_recipient.ts b/javascript/subtle/ecies_hkdf_kem_recipient.ts
new file mode 100644
index 0000000..c97908d
--- /dev/null
+++ b/javascript/subtle/ecies_hkdf_kem_recipient.ts
@@ -0,0 +1,73 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {SecurityException} from '../exception/security_exception';
+
+import * as Bytes from './bytes';
+import * as EllipticCurves from './elliptic_curves';
+import * as Hkdf from './hkdf';
+
+/**
+ * HKDF-based ECIES-KEM (key encapsulation mechanism) for ECIES recipient.
+ */
+export class EciesHkdfKemRecipient {
+  private readonly privateKey_: CryptoKey;
+
+  constructor(privateKey: CryptoKey) {
+    if (!privateKey) {
+      throw new SecurityException('Private key has to be non-null.');
+    }
+
+    // CryptoKey should have the properties type and algorithm.
+    if (privateKey.type !== 'private' || !privateKey.algorithm) {
+      throw new SecurityException('Expected crypto key of type: private.');
+    }
+    this.privateKey_ = privateKey;
+  }
+
+  /**
+   * @param kemToken the public ephemeral point.
+   * @param keySizeInBytes The length of the generated pseudorandom
+   *     string in bytes. The maximal size is 255 * DigestSize, where DigestSize
+   *     is the size of the underlying HMAC.
+   * @param pointFormat The format of the
+   *     public ephemeral point.
+   * @param hkdfHash the name of the hash function. Accepted names are
+   *     SHA-1, SHA-256 and SHA-512.
+   * @param hkdfInfo Context and application specific
+   *     information (can be a zero-length array).
+   * @param opt_hkdfSalt Salt value (a non-secret random
+   *     value). If not provided, it is set to a string of hash length zeros.
+   * @return The KEM key and token.
+   */
+  async decapsulate(
+      kemToken: Uint8Array, keySizeInBytes: number,
+      pointFormat: EllipticCurves.PointFormatType, hkdfHash: string,
+      hkdfInfo: Uint8Array, opt_hkdfSalt?: Uint8Array): Promise<Uint8Array> {
+    const {namedCurve}: Partial<EcKeyAlgorithm> = this.privateKey_.algorithm;
+    if (!namedCurve) {
+      throw new SecurityException('Curve has to be defined.');
+    }
+    const jwk = EllipticCurves.pointDecode(namedCurve, pointFormat, kemToken);
+    const publicKey = await EllipticCurves.importPublicKey('ECDH', jwk);
+    const sharedSecret = await EllipticCurves.computeEcdhSharedSecret(
+        this.privateKey_, publicKey);
+    const hkdfIkm = Bytes.concat(kemToken, sharedSecret);
+    const kemKey = await Hkdf.compute(
+        keySizeInBytes, hkdfHash, hkdfIkm, hkdfInfo, opt_hkdfSalt);
+    return kemKey;
+  }
+}
+
+export async function fromJsonWebKey(jwk: JsonWebKey):
+    Promise<EciesHkdfKemRecipient> {
+  const privateKey = await EllipticCurves.importPrivateKey('ECDH', jwk);
+  return new EciesHkdfKemRecipient(privateKey);
+}
diff --git a/javascript/subtle/ecies_hkdf_kem_recipient_test.js b/javascript/subtle/ecies_hkdf_kem_recipient_test.js
index dbec990..8c687f9 100644
--- a/javascript/subtle/ecies_hkdf_kem_recipient_test.js
+++ b/javascript/subtle/ecies_hkdf_kem_recipient_test.js
@@ -15,12 +15,11 @@
 goog.module('tink.subtle.EciesHkdfKemRecipientTest');
 goog.setTestOnly('tink.subtle.EciesHkdfKemRecipientTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const EciesHkdfKemRecipient = goog.require('tink.subtle.EciesHkdfKemRecipient');
-const EciesHkdfKemSender = goog.require('tink.subtle.EciesHkdfKemSender');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Random = goog.require('tink.subtle.Random');
-
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const {EciesHkdfKemRecipient, fromJsonWebKey: recipientFromJsonWebKey} = goog.require('google3.third_party.tink.javascript.subtle.ecies_hkdf_kem_recipient');
+const {fromJsonWebKey: senderFromJsonWebKey} = goog.require('google3.third_party.tink.javascript.subtle.ecies_hkdf_kem_sender');
 
 describe('ecies hkdf kem recipient test', function() {
   beforeEach(function() {
@@ -37,8 +36,8 @@
     const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
     const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
     const privateKey = await EllipticCurves.exportCryptoKey(keyPair.privateKey);
-    const sender = await EciesHkdfKemSender.newInstance(publicKey);
-    const recipient = await EciesHkdfKemRecipient.newInstance(privateKey);
+    const sender = await senderFromJsonWebKey(publicKey);
+    const recipient = await recipientFromJsonWebKey(privateKey);
     for (let i = 1; i < 20; i++) {
       const keySizeInBytes = i;
       const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
@@ -61,8 +60,8 @@
     const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
     const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
     const privateKey = await EllipticCurves.exportCryptoKey(keyPair.privateKey);
-    const sender = await EciesHkdfKemSender.newInstance(publicKey);
-    const recipient = await EciesHkdfKemRecipient.newInstance(privateKey);
+    const sender = await senderFromJsonWebKey(publicKey);
+    const recipient = await recipientFromJsonWebKey(privateKey);
     const keySizeInBytes = 16;
     const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
     const hkdfHash = 'SHA-256';
@@ -95,7 +94,7 @@
     const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
     const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
     try {
-      await EciesHkdfKemRecipient.newInstance(publicKey);
+      await recipientFromJsonWebKey(publicKey);
       fail('An exception should be thrown.');
     } catch (e) {
     }
@@ -118,7 +117,7 @@
           Bytes.toBase64(new Uint8Array(xLength), /* opt_webSafe = */ true);
       let output;
       try {
-        const recipient = await EciesHkdfKemRecipient.newInstance(privateJwk);
+        const recipient = await recipientFromJsonWebKey(privateJwk);
         const hkdfInfo = Bytes.fromHex(testVector.hkdfInfo);
         const salt = Bytes.fromHex(testVector.salt);
         output = await recipient.decapsulate(
@@ -147,11 +146,13 @@
   });
 
   it('encap decap, different params', async function() {
-    const curveTypes = Object.keys(EllipticCurves.CurveType);
+    const curveTypes = [
+      EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+      EllipticCurves.CurveType.P521
+    ];
     const hashTypes = ['SHA-1', 'SHA-256', 'SHA-512'];
     for (let curve of curveTypes) {
-      const curveString =
-          EllipticCurves.curveToString(EllipticCurves.CurveType[curve]);
+      const curveString = EllipticCurves.curveToString(curve);
       for (let hashType of hashTypes) {
         const keyPair =
             await EllipticCurves.generateKeyPair('ECDH', curveString);
@@ -162,13 +163,13 @@
 
         const publicKey =
             await EllipticCurves.exportCryptoKey(keyPair.publicKey);
-        const sender = await EciesHkdfKemSender.newInstance(publicKey);
+        const sender = await senderFromJsonWebKey(publicKey);
         const kemKeyToken = await sender.encapsulate(
             keySizeInBytes, pointFormat, hashType, hkdfInfo, hkdfSalt);
 
         const privateKey =
             await EllipticCurves.exportCryptoKey(keyPair.privateKey);
-        const recipient = await EciesHkdfKemRecipient.newInstance(privateKey);
+        const recipient = await recipientFromJsonWebKey(privateKey);
         const key = await recipient.decapsulate(
             kemKeyToken['token'], keySizeInBytes, pointFormat, hashType,
             hkdfInfo, hkdfSalt);
@@ -180,17 +181,19 @@
   });
 
   it('encap decap, modified token', async function() {
-    const curveTypes = Object.keys(EllipticCurves.CurveType);
+    const curveTypes = [
+      EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+      EllipticCurves.CurveType.P521
+    ];
     const hashTypes = ['SHA-1', 'SHA-256', 'SHA-512'];
-    for (let crvId of curveTypes) {
-      const curve = EllipticCurves.CurveType[crvId];
+    for (let curve of curveTypes) {
       const curveString = EllipticCurves.curveToString(curve);
       for (let hashType of hashTypes) {
         const keyPair =
             await EllipticCurves.generateKeyPair('ECDH', curveString);
         const privateKey =
             await EllipticCurves.exportCryptoKey(keyPair.privateKey);
-        const recipient = await EciesHkdfKemRecipient.newInstance(privateKey);
+        const recipient = await recipientFromJsonWebKey(privateKey);
         const keySizeInBytes = 32;
         const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
         const hkdfInfo = Random.randBytes(8);
@@ -219,7 +222,7 @@
           Bytes.fromHex(testVector.privateKeyPoint));
       privateJwk['d'] = Bytes.toBase64(
           Bytes.fromHex(testVector.privateKeyValue), /* opt_webSafe = */ true);
-      const recipient = await EciesHkdfKemRecipient.newInstance(privateJwk);
+      const recipient = await recipientFromJsonWebKey(privateJwk);
       const hkdfInfo = Bytes.fromHex(testVector.hkdfInfo);
       const salt = Bytes.fromHex(testVector.salt);
       const output = await recipient.decapsulate(
diff --git a/javascript/subtle/ecies_hkdf_kem_sender.js b/javascript/subtle/ecies_hkdf_kem_sender.js
deleted file mode 100644
index 4f4de45..0000000
--- a/javascript/subtle/ecies_hkdf_kem_sender.js
+++ /dev/null
@@ -1,82 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.EciesHkdfKemSender');
-
-const Bytes = goog.require('tink.subtle.Bytes');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Hkdf = goog.require('tink.subtle.Hkdf');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-
-/**
- * HKDF-based ECIES-KEM (key encapsulation mechanism) for ECIES sender.
- */
-class EciesHkdfKemSender {
-  /**
-   * @param {!webCrypto.CryptoKey} recipientPublicKey
-   */
-  constructor(recipientPublicKey) {
-    if (!recipientPublicKey) {
-      throw new SecurityException('Recipient public key has to be non-null.');
-    }
-    // CryptoKey should have the properties type and algorithm.
-    if (recipientPublicKey.type !== 'public' || !recipientPublicKey.algorithm) {
-      throw new SecurityException('Expected Crypto key of type: public.');
-    }
-    /** @const @private {!webCrypto.CryptoKey} */
-    this.publicKey_ = recipientPublicKey;
-  }
-
-  /**
-   * @param {!webCrypto.JsonWebKey} jwk
-   * @return {!Promise.<!EciesHkdfKemSender>}
-   * @static
-   */
-  static async newInstance(jwk) {
-    const publicKey = await EllipticCurves.importPublicKey('ECDH', jwk);
-    return new EciesHkdfKemSender(publicKey);
-  }
-
-  /**
-   * @param {number} keySizeInBytes The length of the generated pseudorandom
-   *     string in bytes. The maximal size is 255 * DigestSize, where DigestSize
-   *     is the size of the underlying HMAC.
-   * @param {!EllipticCurves.PointFormatType} pointFormat The format of the
-   *     public ephemeral point.
-   * @param {string} hkdfHash the name of the hash function. Accepted names are
-   *     SHA-1, SHA-256 and SHA-512.
-   * @param {!Uint8Array} hkdfInfo Context and application specific
-   *     information (can be a zero-length array).
-   * @param {!Uint8Array=} opt_hkdfSalt Salt value (a non-secret random
-   *     value). If not provided, it is set to a string of hash length zeros.
-   * @return {!Promise.<{key:!Uint8Array, token:!Uint8Array}>} The KEM key and
-   *     token.
-   */
-  async encapsulate(
-      keySizeInBytes, pointFormat, hkdfHash, hkdfInfo, opt_hkdfSalt) {
-    const ephemeralKeyPair = await EllipticCurves.generateKeyPair(
-        'ECDH', this.publicKey_.algorithm['namedCurve']);
-    const sharedSecret = await EllipticCurves.computeEcdhSharedSecret(
-        /** @type {?} */ (ephemeralKeyPair).privateKey, this.publicKey_);
-    const jwk = await EllipticCurves.exportCryptoKey(
-        /** @type {?} */ (ephemeralKeyPair).publicKey);
-    const kemToken = EllipticCurves.pointEncode(jwk.crv, pointFormat, jwk);
-    const hkdfIkm = Bytes.concat(kemToken, sharedSecret);
-    const kemKey = await Hkdf.compute(
-        keySizeInBytes, hkdfHash, hkdfIkm, hkdfInfo, opt_hkdfSalt);
-    return {'key': kemKey, 'token': kemToken};
-  }
-}
-
-exports = EciesHkdfKemSender;
diff --git a/javascript/subtle/ecies_hkdf_kem_sender.ts b/javascript/subtle/ecies_hkdf_kem_sender.ts
new file mode 100644
index 0000000..097b037
--- /dev/null
+++ b/javascript/subtle/ecies_hkdf_kem_sender.ts
@@ -0,0 +1,80 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {SecurityException} from '../exception/security_exception';
+
+import * as Bytes from './bytes';
+import * as EllipticCurves from './elliptic_curves';
+import * as Hkdf from './hkdf';
+
+/**
+ * HKDF-based ECIES-KEM (key encapsulation mechanism) for ECIES sender.
+ */
+export class EciesHkdfKemSender {
+  private readonly publicKey_: CryptoKey;
+
+  constructor(recipientPublicKey: CryptoKey) {
+    if (!recipientPublicKey) {
+      throw new SecurityException('Recipient public key has to be non-null.');
+    }
+
+    // CryptoKey should have the properties type and algorithm.
+    if (recipientPublicKey.type !== 'public' || !recipientPublicKey.algorithm) {
+      throw new SecurityException('Expected Crypto key of type: public.');
+    }
+    this.publicKey_ = recipientPublicKey;
+  }
+
+  /**
+   * @param keySizeInBytes The length of the generated pseudorandom
+   *     string in bytes. The maximal size is 255 * DigestSize, where DigestSize
+   *     is the size of the underlying HMAC.
+   * @param pointFormat The format of the
+   *     public ephemeral point.
+   * @param hkdfHash the name of the hash function. Accepted names are
+   *     SHA-1, SHA-256 and SHA-512.
+   * @param hkdfInfo Context and application specific
+   *     information (can be a zero-length array).
+   * @param opt_hkdfSalt Salt value (a non-secret random
+   *     value). If not provided, it is set to a string of hash length zeros.
+   * @return The KEM key and
+   *     token.
+   */
+  async encapsulate(
+      keySizeInBytes: number, pointFormat: EllipticCurves.PointFormatType,
+      hkdfHash: string, hkdfInfo: Uint8Array, opt_hkdfSalt?: Uint8Array):
+      Promise<{key: Uint8Array, token: Uint8Array}> {
+    const {namedCurve}: Partial<EcKeyAlgorithm> = this.publicKey_.algorithm;
+    if (!namedCurve) {
+      throw new SecurityException('Curve has to be defined.');
+    }
+    const ephemeralKeyPair =
+        await EllipticCurves.generateKeyPair('ECDH', namedCurve);
+    const sharedSecret = await EllipticCurves.computeEcdhSharedSecret(
+        ephemeralKeyPair.privateKey, this.publicKey_);
+    const jwk =
+        await EllipticCurves.exportCryptoKey(ephemeralKeyPair.publicKey);
+    const {crv} = jwk;
+    if (!crv) {
+      throw new SecurityException('Curve has to be defined.');
+    }
+    const kemToken = EllipticCurves.pointEncode(crv, pointFormat, jwk);
+    const hkdfIkm = Bytes.concat(kemToken, sharedSecret);
+    const kemKey = await Hkdf.compute(
+        keySizeInBytes, hkdfHash, hkdfIkm, hkdfInfo, opt_hkdfSalt);
+    return {'key': kemKey, 'token': kemToken};
+  }
+}
+
+export async function fromJsonWebKey(jwk: JsonWebKey):
+    Promise<EciesHkdfKemSender> {
+  const publicKey = await EllipticCurves.importPublicKey('ECDH', jwk);
+  return new EciesHkdfKemSender(publicKey);
+}
diff --git a/javascript/subtle/ecies_hkdf_kem_sender_test.js b/javascript/subtle/ecies_hkdf_kem_sender_test.js
index 765afee..3442e2a 100644
--- a/javascript/subtle/ecies_hkdf_kem_sender_test.js
+++ b/javascript/subtle/ecies_hkdf_kem_sender_test.js
@@ -15,17 +15,16 @@
 goog.module('tink.subtle.EciesHkdfKemSenderTest');
 goog.setTestOnly('tink.subtle.EciesHkdfKemSenderTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const EciesHkdfKemSender = goog.require('tink.subtle.EciesHkdfKemSender');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Random = goog.require('tink.subtle.Random');
-
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const {EciesHkdfKemSender, fromJsonWebKey} = goog.require('google3.third_party.tink.javascript.subtle.ecies_hkdf_kem_sender');
 
 describe('ecies hkdf kem sender test', function() {
   it('encapsulate, always generate random key', async function() {
     const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
     const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
-    const sender = await EciesHkdfKemSender.newInstance(publicKey);
+    const sender = await fromJsonWebKey(publicKey);
     const keySizeInBytes = 32;
     const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
     const hkdfHash = 'SHA-256';
@@ -46,7 +45,7 @@
   it('encapsulate, non integer key size', async function() {
     const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
     const publicKey = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
-    const sender = await EciesHkdfKemSender.newInstance(publicKey);
+    const sender = await fromJsonWebKey(publicKey);
     const pointFormat = EllipticCurves.PointFormatType.UNCOMPRESSED;
     const hkdfHash = 'SHA-256';
     const hkdfInfo = Random.randBytes(32);
@@ -68,19 +67,20 @@
   });
 
   it('new instance, invalid parameters', async function() {
-    // Test newInstance with public key instead private key.
+    // Test fromJsonWebKey with public key instead private key.
     const keyPair = await EllipticCurves.generateKeyPair('ECDH', 'P-256');
     const privateKey = await EllipticCurves.exportCryptoKey(keyPair.privateKey);
     try {
-      await EciesHkdfKemSender.newInstance(privateKey);
+      await fromJsonWebKey(privateKey);
       fail('An exception should be thrown.');
     } catch (e) {
     }
   });
 
   it('new instance, invalid public key', async function() {
-    for (let crv of Object.keys(EllipticCurves.CurveType)) {
-      const curve = EllipticCurves.CurveType[crv];
+    for (let curve
+             of [EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+                 EllipticCurves.CurveType.P521]) {
       const crvString = EllipticCurves.curveToString(curve);
       const keyPair = await EllipticCurves.generateKeyPair('ECDH', crvString);
       const publicJwk = await EllipticCurves.exportCryptoKey(keyPair.publicKey);
@@ -93,7 +93,7 @@
       const hkdfInfo = Random.randBytes(10);
       const salt = Random.randBytes(8);
       try {
-        const sender = await EciesHkdfKemSender.newInstance(publicJwk);
+        const sender = await fromJsonWebKey(publicJwk);
         await sender.encapsulate(
             /* keySizeInBytes = */ 32,
             EllipticCurves.PointFormatType.UNCOMPRESSED,
diff --git a/javascript/subtle/elliptic_curves.js b/javascript/subtle/elliptic_curves.js
deleted file mode 100644
index eee48e9..0000000
--- a/javascript/subtle/elliptic_curves.js
+++ /dev/null
@@ -1,532 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-/**
- * @fileoverview Common enums.
- */
-
-goog.module('tink.subtle.EllipticCurves');
-
-const Bytes = goog.require('tink.subtle.Bytes');
-const {InvalidArgumentsException} = goog.require('google3.third_party.tink.javascript.exception.invalid_arguments_exception');
-
-/**
- * Supported elliptic curves.
- * @enum {number}
- */
-const CurveType = {
-  P256: 1,
-  P384: 2,
-  P521: 3,
-};
-
-/**
- * Supported point format.
- * @enum {number}
- */
-const PointFormatType = {
-  UNCOMPRESSED: 1,
-  COMPRESSED: 2,
-  // Like UNCOMPRESSED but without the \x04 prefix. Crunchy uses this format.
-  // DO NOT USE unless you are a Crunchy user moving to Tink.
-  DO_NOT_USE_CRUNCHY_UNCOMPRESSED: 3,
-};
-
-/**
- * Supported ECDSA signature encoding.
- * @enum {number}
- */
-const EcdsaSignatureEncodingType = {
-  // 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.
-  DER: 1,
-  // 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.
-  IEEE_P1363: 2,
-};
-
-/**
- * Transform an ECDSA signature in DER encoding to IEEE P1363 encoding.
- *
- * @param {!Uint8Array} der the ECDSA signature in DER encoding
- * @param {number} ieeeLength the length of the ECDSA signature in IEEE
- *     encoding. This is usually 2 * size of the elliptic curve field.
- * @return {!Uint8Array} ECDSA signature in IEEE encoding
- */
-const ecdsaDer2Ieee = function(der, ieeeLength) {
-  if (!isValidDerEcdsaSignature(der)) {
-    throw new InvalidArgumentsException('invalid DER signature');
-  }
-  if (!Number.isInteger(ieeeLength) || ieeeLength < 0) {
-    throw new InvalidArgumentsException(
-        'ieeeLength must be a nonnegative integer');
-  }
-  const ieee = new Uint8Array(ieeeLength);
-  const length = der[1] & 0xff;
-  let offset = 1 /* 0x30 */ + 1 /* totalLength */;
-  if (length >= 128) {
-    offset++;  // Long form length
-  }
-  offset++;  // 0x02
-  const rLength = der[offset++];
-  let extraZero = 0;
-  if (der[offset] === 0) {
-    extraZero = 1;
-  }
-  const rOffset = ieeeLength / 2 - rLength + extraZero;
-  ieee.set(der.subarray(offset + extraZero, offset + rLength), rOffset);
-  offset += rLength /* r byte array */ + 1 /* 0x02 */;
-  const sLength = der[offset++];
-  extraZero = 0;
-  if (der[offset] === 0) {
-    extraZero = 1;
-  }
-  const sOffset = ieeeLength - sLength + extraZero;
-  ieee.set(der.subarray(offset + extraZero, offset + sLength), sOffset);
-  return ieee;
-};
-
-/**
- * Transform an ECDSA signature in IEEE 1363 encoding to DER encoding.
- *
- * @param {!Uint8Array} ieee the ECDSA signature in IEEE encoding
- * @return {!Uint8Array} ECDSA signature in DER encoding
- */
-const ecdsaIeee2Der = function(ieee) {
-  if (ieee.length % 2 != 0 || ieee.length == 0 || ieee.length > 132) {
-    throw new InvalidArgumentsException(
-        'Invalid IEEE P1363 signature encoding. Length: ' + ieee.length);
-  }
-  const r = toUnsignedBigNum(ieee.subarray(0, ieee.length / 2));
-  const s = toUnsignedBigNum(ieee.subarray(ieee.length / 2, ieee.length));
-
-  let offset = 0;
-  const length = 1 + 1 + r.length + 1 + 1 + s.length;
-  let der;
-  if (length >= 128) {
-    der = new Uint8Array(length + 3);
-    der[offset++] = 0x30;
-    der[offset++] = 0x80 + 0x01;
-    der[offset++] = length;
-  } else {
-    der = new Uint8Array(length + 2);
-    der[offset++] = 0x30;
-    der[offset++] = length;
-  }
-  der[offset++] = 0x02;
-  der[offset++] = r.length;
-  der.set(r, offset);
-  offset += r.length;
-  der[offset++] = 0x02;
-  der[offset++] = s.length;
-  der.set(s, offset);
-  return der;
-};
-
-/**
- * Validate that the ECDSA signature is in DER encoding, based on
- * https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki.
- *
- * @param {!Uint8Array} sig an ECDSA siganture
- * @return {boolean}
- */
-const isValidDerEcdsaSignature = function(sig) {
-  // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
-  // * total-length: 1-byte or 2-byte length descriptor of everything that
-  // follows.
-  // * R-length: 1-byte length descriptor of the R value that follows.
-  // * R: arbitrary-length big-endian encoded R value. It must use the shortest
-  //   possible encoding for a positive integers (which means no null bytes at
-  //   the start, except a single one when the next byte has its highest bit
-  //   set).
-  // * S-length: 1-byte length descriptor of the S value that follows.
-  // * S: arbitrary-length big-endian encoded S value. The same rules apply.
-  if (sig.length < 1 /* 0x30 */
-          + 1        /* total-length */
-          + 1        /* 0x02 */
-          + 1        /* R-length */
-          + 1        /* R */
-          + 1        /* 0x02 */
-          + 1        /* S-length */
-          + 1 /* S */) {
-    // Signature is too short.
-    return false;
-  }
-
-  // Checking bytes from left to right.
-
-  // byte #1: a signature is of type 0x30 (compound).
-  if (sig[0] != 0x30) {
-    return false;
-  }
-
-  // byte #2 and maybe #3: the total length of the signature.
-  let totalLen = sig[1] & 0xff;
-  let totalLenLen =
-      1;  // the length of the total length field, could be 2-byte.
-  if (totalLen == 129) {
-    // The signature is >= 128 bytes thus total length field is in long-form
-    // encoding and occupies 2 bytes.
-    totalLenLen = 2;
-    // byte #3 is the total length.
-    totalLen = sig[2] & 0xff;
-    if (totalLen < 128) {
-      // Length in long-form encoding must be >= 128.
-      return false;
-    }
-  } else if (totalLen == 128 || totalLen > 129) {
-    // Impossible values for the second byte.
-    return false;
-  }
-
-  // Make sure the length covers the entire sig.
-  if (totalLen != sig.length - 1 - totalLenLen) {
-    return false;
-  }
-
-  // Start checking R.
-  // Check whether the R element is an integer.
-  if (sig[1 + totalLenLen] != 0x02) {
-    return false;
-  }
-  // Extract the length of the R element.
-  const rLen = sig[1 /* 0x30 */ + totalLenLen + 1 /* 0x02 */] & 0xff;
-  // Make sure the length of the S element is still inside the signature.
-  if (1 /* 0x30 */ + totalLenLen + 1 /* 0x02 */ + 1 /* rLen */ + rLen +
-          1 /* 0x02 */
-      >= sig.length) {
-    return false;
-  }
-  // Zero-length integers are not allowed for R.
-  if (rLen == 0) {
-    return false;
-  }
-  // Negative numbers are not allowed for R.
-  if ((sig[3 + totalLenLen] & 0xff) >= 128) {
-    return false;
-  }
-  // Null bytes at the start of R are not allowed, unless R would
-  // otherwise be interpreted as a negative number.
-  if (rLen > 1 && (sig[3 + totalLenLen] == 0x00) &&
-      ((sig[4 + totalLenLen] & 0xff) < 128)) {
-    return false;
-  }
-
-  // Start checking S.
-  // Check whether the S element is an integer.
-  if (sig[3 + totalLenLen + rLen] != 0x02) {
-    return false;
-  }
-  // Extract the length of the S element.
-  const sLen = sig[1 /* 0x30 */ + totalLenLen + 1 /* 0x02 */ + 1 /* rLen */ +
-                   rLen + 1 /* 0x02 */] &
-      0xff;
-  // Verify that the length of the signature matches the sum of the length of
-  // the elements.
-  if (1                     /* 0x30 */
-          + totalLenLen + 1 /* 0x02 */
-          + 1               /* rLen */
-          + rLen + 1        /* 0x02 */
-          + 1               /* sLen */
-          + sLen !=
-      sig.length) {
-    return false;
-  }
-  // Zero-length integers are not allowed for S.
-  if (sLen == 0) {
-    return false;
-  }
-  // Negative numbers are not allowed for S.
-  if ((sig[5 + totalLenLen + rLen] & 0xff) >= 128) {
-    return false;
-  }
-  // Null bytes at the start of S are not allowed, unless S would
-  // otherwise be interpreted as a negative number.
-  if (sLen > 1 && (sig[5 + totalLenLen + rLen] == 0x00) &&
-      ((sig[6 + totalLenLen + rLen] & 0xff) < 128)) {
-    return false;
-  }
-
-  return true;
-};
-
-/**
- * Transform a big integer in big endian to minimal unsigned form which has
- * no extra zero at the beginning except when the highest bit is set.
- *
- * @param {!Uint8Array} bytes
- * @return {!Uint8Array}
- */
-const toUnsignedBigNum = function(bytes) {
-  // Remove zero prefixes.
-  let start = 0;
-  while (start < bytes.length && bytes[start] == 0) {
-    start++;
-  }
-  if (start == bytes.length) {
-    start = bytes.length - 1;
-  }
-
-  let extraZero = 0;
-  // If the 1st bit is not zero, add 1 zero byte.
-  if ((bytes[start] & 0x80) == 0x80) {
-    // Add extra zero.
-    extraZero = 1;
-  }
-  const res = new Uint8Array(bytes.length - start + extraZero);
-  res.set(bytes.subarray(start), extraZero);
-  return res;
-};
-
-/**
- * @param {!CurveType} curve
- * @return {string}
- */
-const curveToString = function(curve) {
-  switch (curve) {
-    case CurveType.P256:
-      return 'P-256';
-    case CurveType.P384:
-      return 'P-384';
-    case CurveType.P521:
-      return 'P-521';
-  }
-  throw new InvalidArgumentsException('unknown curve: ' + curve);
-};
-
-/**
- * @param {string} curve
- * @return {!CurveType}
- */
-const curveFromString = function(curve) {
-  switch (curve) {
-    case 'P-256':
-      return CurveType.P256;
-    case 'P-384':
-      return CurveType.P384;
-    case 'P-521':
-      return CurveType.P521;
-  }
-  throw new InvalidArgumentsException('unknown curve: ' + curve);
-};
-
-/**
- * @param {string} curve
- * @param {!PointFormatType} format
- * @param {!webCrypto.JsonWebKey} point
- * @return {!Uint8Array}
- */
-const pointEncode = function(curve, format, point) {
-  const fieldSize = fieldSizeInBytes(curveFromString(curve));
-  switch (format) {
-    case PointFormatType.UNCOMPRESSED:
-      let result = new Uint8Array(1 + 2 * fieldSize);
-      result[0] = 0x04;
-      result.set(Bytes.fromBase64(point.x, /* opt_webSafe = */ true), 1);
-      result.set(
-          Bytes.fromBase64(point.y, /* opt_webSafe = */ true), 1 + fieldSize);
-      return result;
-  }
-  throw new InvalidArgumentsException('invalid format');
-};
-
-/**
- * @param {string} curve
- * @param {!PointFormatType} format
- * @param {!Uint8Array} point
- * @return {!webCrypto.JsonWebKey}
- */
-const pointDecode = function(curve, format, point) {
-  const fieldSize = fieldSizeInBytes(curveFromString(curve));
-  switch (format) {
-    case PointFormatType.UNCOMPRESSED:
-      if (point.length != 1 + 2 * fieldSize || point[0] != 0x04) {
-        throw new InvalidArgumentsException('invalid point');
-      }
-      let result = /** @type {!webCrypto.JsonWebKey} */ ({
-        'kty': 'EC',
-        'crv': curve,
-        'x': Bytes.toBase64(
-            new Uint8Array(point.subarray(1, 1 + fieldSize)),
-            true /* websafe */),
-        'y': Bytes.toBase64(
-            new Uint8Array(point.subarray(1 + fieldSize, point.length)),
-            true /* websafe */),
-        'ext': true,
-      });
-      return result;
-  }
-  throw new InvalidArgumentsException('invalid format');
-};
-
-/**
- * @param {!CurveType} curve
- * @param {!Uint8Array} x
- * @param {!Uint8Array} y
- * @param {?Uint8Array=} d
- *
- * @return {!webCrypto.JsonWebKey}
- */
-const getJsonWebKey = function(curve, x, y, d) {
-  const key = /** @type {!webCrypto.JsonWebKey} */ ({
-    'kty': 'EC',
-    'crv': curveToString(curve),
-    'x': Bytes.toBase64(x, true /* websafe */),
-    'y': Bytes.toBase64(y, true /* websafe */),
-    'ext': true,
-  });
-  if (d) {
-    key['d'] = Bytes.toBase64(d, true /* websafe */);
-  }
-  return key;
-};
-
-/**
- * @param {!CurveType} curve
- * @return {number}
- */
-const fieldSizeInBytes = function(curve) {
-  switch (curve) {
-    case CurveType.P256:
-      return 32;
-    case CurveType.P384:
-      return 48;
-    case CurveType.P521:
-      return 66;
-  }
-  throw new InvalidArgumentsException('unknown curve: ' + curve);
-};
-
-/**
- * @param {!CurveType} curve
- * @param {!PointFormatType} pointFormat
- *
- * @return {number}
- */
-const encodingSizeInBytes = function(curve, pointFormat) {
-  switch (pointFormat) {
-    case PointFormatType.UNCOMPRESSED:
-      return 2 * fieldSizeInBytes(curve) + 1;
-    case PointFormatType.COMPRESSED:
-      return fieldSizeInBytes(curve) + 1;
-    case PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED:
-      return 2 * fieldSizeInBytes(curve);
-  }
-  throw new InvalidArgumentsException('invalid format');
-};
-
-/**
- * @param {!webCrypto.CryptoKey} privateKey
- * @param {!webCrypto.CryptoKey} publicKey
- * @return {!Promise<!Uint8Array>}
- */
-const computeEcdhSharedSecret = async function(privateKey, publicKey) {
-  const ecdhParams =
-      /** @type {!webCrypto.AlgorithmIdentifier} */ (privateKey.algorithm);
-  ecdhParams['public'] = publicKey;
-  const fieldSizeInBits =
-      8 * fieldSizeInBytes(curveFromString(ecdhParams['namedCurve']));
-  const sharedSecret = await window.crypto.subtle.deriveBits(
-      ecdhParams, privateKey, fieldSizeInBits);
-  return new Uint8Array(sharedSecret);
-};
-
-/**
- * @param {string} algorithm
- * @param {string} curve
- * @return {!Promise<!webCrypto.CryptoKeyPair>}
- */
-const generateKeyPair = async function(algorithm, curve) {
-  if (algorithm != 'ECDH' && algorithm != 'ECDSA') {
-    throw new InvalidArgumentsException(
-        'algorithm must be either ECDH or ECDSA');
-  }
-  const params = /** @type {!webCrypto.AlgorithmIdentifier} */ (
-      {'name': algorithm, 'namedCurve': curve});
-  const ephemeralKeyPair = await window.crypto.subtle.generateKey(
-      params, true /* extractable */,
-      algorithm == 'ECDH' ? ['deriveKey', 'deriveBits'] :
-                            ['sign', 'verify'] /* usage */);
-  return /** @type {!webCrypto.CryptoKeyPair} */ (ephemeralKeyPair);
-};
-
-/**
- * @param {!webCrypto.CryptoKey} cryptoKey
- * @return {!Promise<!webCrypto.JsonWebKey>}
- */
-const exportCryptoKey = async function(cryptoKey) {
-  const jwk = await window.crypto.subtle.exportKey('jwk', cryptoKey);
-  return /** @type {!webCrypto.JsonWebKey} */ (jwk);
-};
-
-/**
- * @param {string} algorithm
- * @param {!webCrypto.JsonWebKey} jwk
- * @return {!Promise<!webCrypto.CryptoKey>}
- */
-const importPublicKey = async function(algorithm, jwk) {
-  if (algorithm != 'ECDH' && algorithm != 'ECDSA') {
-    throw new InvalidArgumentsException(
-        'algorithm must be either ECDH or ECDSA');
-  }
-  const publicKey = await window.crypto.subtle.importKey(
-      'jwk' /* format */, jwk,
-      {'name': algorithm, 'namedCurve': jwk.crv} /* algorithm */,
-      true /* extractable */,
-      algorithm == 'ECDH' ? [] : ['verify'] /* usage */);
-  return publicKey;
-};
-
-/**
- * @param {string} algorithm
- * @param {!webCrypto.JsonWebKey} jwk
- * @return {!Promise<!webCrypto.CryptoKey>}
- */
-const importPrivateKey = async function(algorithm, jwk) {
-  if (algorithm != 'ECDH' && algorithm != 'ECDSA') {
-    throw new InvalidArgumentsException(
-        'algorithm must be either ECDH or ECDSA');
-  }
-  const privateKey = await window.crypto.subtle.importKey(
-      'jwk' /* format */, jwk /* key material */,
-      {'name': algorithm, 'namedCurve': jwk.crv} /* algorithm */,
-      true /* extractable */,
-      algorithm == 'ECDH' ? ['deriveKey', 'deriveBits'] : ['sign'] /* usage */);
-  return privateKey;
-};
-
-exports = {
-  CurveType,
-  EcdsaSignatureEncodingType,
-  PointFormatType,
-  computeEcdhSharedSecret,
-  curveToString,
-  curveFromString,
-  ecdsaDer2Ieee,
-  ecdsaIeee2Der,
-  getJsonWebKey,
-  isValidDerEcdsaSignature,
-  encodingSizeInBytes,
-  exportCryptoKey,
-  fieldSizeInBytes,
-  generateKeyPair,
-  importPrivateKey,
-  importPublicKey,
-  pointDecode,
-  pointEncode,
-};
diff --git a/javascript/subtle/elliptic_curves.ts b/javascript/subtle/elliptic_curves.ts
new file mode 100644
index 0000000..3e76be9
--- /dev/null
+++ b/javascript/subtle/elliptic_curves.ts
@@ -0,0 +1,542 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @fileoverview Common enums.
+ */
+
+import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
+
+import * as Bytes from './bytes';
+
+/**
+ * Supported elliptic curves.
+ */
+export enum CurveType {
+  P256 = 1,
+  P384,
+  P521
+}
+
+/**
+ * Supported point format.
+ */
+export enum PointFormatType {
+  UNCOMPRESSED = 1,
+  COMPRESSED,
+
+  // Like UNCOMPRESSED but without the \x04 prefix. Crunchy uses this format.
+  // DO NOT USE unless you are a Crunchy user moving to Tink.
+  DO_NOT_USE_CRUNCHY_UNCOMPRESSED
+}
+
+/**
+ * Supported ECDSA signature encoding.
+ */
+export enum EcdsaSignatureEncodingType {
+
+  // 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.
+  DER = 1,
+
+  // 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.
+  IEEE_P1363
+}
+
+/**
+ * Transform an ECDSA signature in DER encoding to IEEE P1363 encoding.
+ *
+ * @param der the ECDSA signature in DER encoding
+ * @param ieeeLength the length of the ECDSA signature in IEEE
+ *     encoding. This is usually 2 * size of the elliptic curve field.
+ * @return ECDSA signature in IEEE encoding
+ */
+export function ecdsaDer2Ieee(der: Uint8Array, ieeeLength: number): Uint8Array {
+  if (!isValidDerEcdsaSignature(der)) {
+    throw new InvalidArgumentsException('invalid DER signature');
+  }
+  if (!Number.isInteger(ieeeLength) || ieeeLength < 0) {
+    throw new InvalidArgumentsException(
+        'ieeeLength must be a nonnegative integer');
+  }
+  const ieee = new Uint8Array(ieeeLength);
+  const length = der[1] & 255;
+  let offset = 1 +
+      /* 0x30 */
+      1;
+
+  /* totalLength */
+  if (length >= 128) {
+    offset++;
+  }
+
+  // Long form length
+  offset++;
+
+  // 0x02
+  const rLength = der[offset++];
+  let extraZero = 0;
+  if (der[offset] === 0) {
+    extraZero = 1;
+  }
+  const rOffset = ieeeLength / 2 - rLength + extraZero;
+  ieee.set(der.subarray(offset + extraZero, offset + rLength), rOffset);
+  offset += rLength +
+      /* r byte array */
+      1;
+
+  /* 0x02 */
+  const sLength = der[offset++];
+  extraZero = 0;
+  if (der[offset] === 0) {
+    extraZero = 1;
+  }
+  const sOffset = ieeeLength - sLength + extraZero;
+  ieee.set(der.subarray(offset + extraZero, offset + sLength), sOffset);
+  return ieee;
+}
+
+/**
+ * Transform an ECDSA signature in IEEE 1363 encoding to DER encoding.
+ *
+ * @param ieee the ECDSA signature in IEEE encoding
+ * @return ECDSA signature in DER encoding
+ */
+export function ecdsaIeee2Der(ieee: Uint8Array): Uint8Array {
+  if (ieee.length % 2 != 0 || ieee.length == 0 || ieee.length > 132) {
+    throw new InvalidArgumentsException(
+        'Invalid IEEE P1363 signature encoding. Length: ' + ieee.length);
+  }
+  const r = toUnsignedBigNum(ieee.subarray(0, ieee.length / 2));
+  const s = toUnsignedBigNum(ieee.subarray(ieee.length / 2, ieee.length));
+  let offset = 0;
+  const length = 1 + 1 + r.length + 1 + 1 + s.length;
+  let der;
+  if (length >= 128) {
+    der = new Uint8Array(length + 3);
+    der[offset++] = 48;
+    der[offset++] = 128 + 1;
+    der[offset++] = length;
+  } else {
+    der = new Uint8Array(length + 2);
+    der[offset++] = 48;
+    der[offset++] = length;
+  }
+  der[offset++] = 2;
+  der[offset++] = r.length;
+  der.set(r, offset);
+  offset += r.length;
+  der[offset++] = 2;
+  der[offset++] = s.length;
+  der.set(s, offset);
+  return der;
+}
+
+/**
+ * Validate that the ECDSA signature is in DER encoding, based on
+ * https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki.
+ *
+ * @param sig an ECDSA siganture
+ */
+export function isValidDerEcdsaSignature(sig: Uint8Array): boolean {
+  // Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S]
+  // * total-length: 1-byte or 2-byte length descriptor of everything that
+  // follows.
+  // * R-length: 1-byte length descriptor of the R value that follows.
+  // * R: arbitrary-length big-endian encoded R value. It must use the shortest
+  //   possible encoding for a positive integers (which means no null bytes at
+  //   the start, except a single one when the next byte has its highest bit
+  //   set).
+  // * S-length: 1-byte length descriptor of the S value that follows.
+  // * S: arbitrary-length big-endian encoded S value. The same rules apply.
+  /* S */
+  if (sig.length < 1 +
+          /* 0x30 */
+          1 +
+          /* total-length */
+          1 +
+          /* 0x02 */
+          1 +
+          /* R-length */
+          1 +
+          /* R */
+          1 +
+          /* 0x02 */
+          1 +
+          /* S-length */
+          1) {
+    // Signature is too short.
+    return false;
+  }
+
+  // Checking bytes from left to right.
+
+  // byte #1: a signature is of type 0x30 (compound).
+  if (sig[0] != 48) {
+    return false;
+  }
+
+  // byte #2 and maybe #3: the total length of the signature.
+  let totalLen = sig[1] & 255;
+  let totalLenLen = 1;
+
+  // the length of the total length field, could be 2-byte.
+  if (totalLen == 129) {
+    // The signature is >= 128 bytes thus total length field is in long-form
+    // encoding and occupies 2 bytes.
+    totalLenLen = 2;
+
+    // byte #3 is the total length.
+    totalLen = sig[2] & 255;
+    if (totalLen < 128) {
+      // Length in long-form encoding must be >= 128.
+      return false;
+    }
+  } else if (totalLen == 128 || totalLen > 129) {
+    // Impossible values for the second byte.
+    return false;
+  }
+
+
+  // Make sure the length covers the entire sig.
+  if (totalLen != sig.length - 1 - totalLenLen) {
+    return false;
+  }
+
+  // Start checking R.
+  // Check whether the R element is an integer.
+  if (sig[1 + totalLenLen] != 2) {
+    return false;
+  }
+
+  // Extract the length of the R element.
+  const rLen = sig[1 +
+                   /* 0x30 */
+                   totalLenLen + 1] &
+      /* 0x02 */
+      255;
+
+  // Make sure the length of the S element is still inside the signature.
+  if (1 +
+          /* 0x30 */
+          totalLenLen + 1 +
+          /* 0x02 */
+          1 +
+          /* rLen */
+          rLen + 1 >=
+      /* 0x02 */
+      sig.length) {
+    return false;
+  }
+
+  // Zero-length integers are not allowed for R.
+  if (rLen == 0) {
+    return false;
+  }
+
+  // Negative numbers are not allowed for R.
+  if ((sig[3 + totalLenLen] & 255) >= 128) {
+    return false;
+  }
+
+  // Null bytes at the start of R are not allowed, unless R would
+  // otherwise be interpreted as a negative number.
+  if (rLen > 1 && sig[3 + totalLenLen] == 0 &&
+      (sig[4 + totalLenLen] & 255) < 128) {
+    return false;
+  }
+
+  // Start checking S.
+  // Check whether the S element is an integer.
+  if (sig[3 + totalLenLen + rLen] != 2) {
+    return false;
+  }
+
+  // Extract the length of the S element.
+  const sLen = sig[1 +
+                   /* 0x30 */
+                   totalLenLen + 1 +
+                   /* 0x02 */
+                   1 +
+                   /* rLen */
+                   rLen + 1] &
+      /* 0x02 */
+      255;
+
+  // Verify that the length of the signature matches the sum of the length of
+  // the elements.
+  if (1 +
+          /* 0x30 */
+          totalLenLen + 1 +
+          /* 0x02 */
+          1 +
+          /* rLen */
+          rLen + 1 +
+          /* 0x02 */
+          1 +
+          /* sLen */
+          sLen !=
+      sig.length) {
+    return false;
+  }
+
+  // Zero-length integers are not allowed for S.
+  if (sLen == 0) {
+    return false;
+  }
+
+  // Negative numbers are not allowed for S.
+  if ((sig[5 + totalLenLen + rLen] & 255) >= 128) {
+    return false;
+  }
+
+  // Null bytes at the start of S are not allowed, unless S would
+  // otherwise be interpreted as a negative number.
+  if (sLen > 1 && sig[5 + totalLenLen + rLen] == 0 &&
+      (sig[6 + totalLenLen + rLen] & 255) < 128) {
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Transform a big integer in big endian to minimal unsigned form which has
+ * no extra zero at the beginning except when the highest bit is set.
+ *
+ */
+function toUnsignedBigNum(bytes: Uint8Array): Uint8Array {
+  // Remove zero prefixes.
+  let start = 0;
+  while (start < bytes.length && bytes[start] == 0) {
+    start++;
+  }
+  if (start == bytes.length) {
+    start = bytes.length - 1;
+  }
+  let extraZero = 0;
+
+  // If the 1st bit is not zero, add 1 zero byte.
+  if ((bytes[start] & 128) == 128) {
+    // Add extra zero.
+    extraZero = 1;
+  }
+  const res = new Uint8Array(bytes.length - start + extraZero);
+  res.set(bytes.subarray(start), extraZero);
+  return res;
+}
+
+export function curveToString(curve: CurveType): string {
+  switch (curve) {
+    case CurveType.P256:
+      return 'P-256';
+    case CurveType.P384:
+      return 'P-384';
+    case CurveType.P521:
+      return 'P-521';
+  }
+  throw new InvalidArgumentsException('unknown curve: ' + curve);
+}
+
+export function curveFromString(curve: string): CurveType {
+  switch (curve) {
+    case 'P-256':
+      return CurveType.P256;
+    case 'P-384':
+      return CurveType.P384;
+    case 'P-521':
+      return CurveType.P521;
+  }
+  throw new InvalidArgumentsException('unknown curve: ' + curve);
+}
+
+export function pointEncode(
+    curve: string, format: PointFormatType, point: JsonWebKey): Uint8Array {
+  const fieldSize = fieldSizeInBytes(curveFromString(curve));
+  switch (format) {
+    case PointFormatType.UNCOMPRESSED:
+      const {x, y} = point;
+      if (x === undefined) {
+        throw new InvalidArgumentsException('x must be provided');
+      }
+      if (y === undefined) {
+        throw new InvalidArgumentsException('y must be provided');
+      }
+      const result = new Uint8Array(1 + 2 * fieldSize);
+      result[0] = 4;
+      result.set(
+          /* opt_webSafe = */
+          Bytes.fromBase64(x, true), 1);
+      result.set(
+          /* opt_webSafe = */
+          Bytes.fromBase64(y, true), 1 + fieldSize);
+      return result;
+  }
+  throw new InvalidArgumentsException('invalid format');
+}
+
+export function pointDecode(
+    curve: string, format: PointFormatType, point: Uint8Array): JsonWebKey {
+  const fieldSize = fieldSizeInBytes(curveFromString(curve));
+  switch (format) {
+    case PointFormatType.UNCOMPRESSED:
+      if (point.length != 1 + 2 * fieldSize || point[0] != 4) {
+        throw new InvalidArgumentsException('invalid point');
+      }
+      const result = ({
+        'kty': 'EC',
+        'crv': curve,
+        'x': Bytes.toBase64(
+            new Uint8Array(point.subarray(1, 1 + fieldSize)),
+            /* websafe */
+            true),
+        'y': Bytes.toBase64(
+            new Uint8Array(point.subarray(1 + fieldSize, point.length)),
+            /* websafe */
+            true),
+        'ext': true
+      } as JsonWebKey);
+      return result;
+  }
+  throw new InvalidArgumentsException('invalid format');
+}
+
+export function getJsonWebKey(
+    curve: CurveType, x: Uint8Array, y: Uint8Array,
+    d?: Uint8Array|null): JsonWebKey {
+  const key = ({
+    'kty': 'EC',
+    'crv': curveToString(curve),
+    'x': Bytes.toBase64(
+        x,
+        /* websafe */
+        true),
+    'y': Bytes.toBase64(
+        y,
+        /* websafe */
+        true),
+    'ext': true
+  } as JsonWebKey);
+  if (d) {
+    key['d'] = Bytes.toBase64(
+        d,
+        /* websafe */
+        true);
+  }
+  return key;
+}
+
+export function fieldSizeInBytes(curve: CurveType): number {
+  switch (curve) {
+    case CurveType.P256:
+      return 32;
+    case CurveType.P384:
+      return 48;
+    case CurveType.P521:
+      return 66;
+  }
+  throw new InvalidArgumentsException('unknown curve: ' + curve);
+}
+
+export function encodingSizeInBytes(
+    curve: CurveType, pointFormat: PointFormatType): number {
+  switch (pointFormat) {
+    case PointFormatType.UNCOMPRESSED:
+      return 2 * fieldSizeInBytes(curve) + 1;
+    case PointFormatType.COMPRESSED:
+      return fieldSizeInBytes(curve) + 1;
+    case PointFormatType.DO_NOT_USE_CRUNCHY_UNCOMPRESSED:
+      return 2 * fieldSizeInBytes(curve);
+  }
+  throw new InvalidArgumentsException('invalid format');
+}
+
+export async function computeEcdhSharedSecret(
+    privateKey: CryptoKey, publicKey: CryptoKey): Promise<Uint8Array> {
+  const {namedCurve}: Partial<EcKeyImportParams> = privateKey.algorithm;
+  if (!namedCurve) {
+    throw new InvalidArgumentsException('namedCurve must be provided');
+  }
+  const ecdhParams = {'public': publicKey, ...privateKey.algorithm};
+  const fieldSizeInBits = 8 * fieldSizeInBytes(curveFromString(namedCurve));
+  const sharedSecret = await window.crypto.subtle.deriveBits(
+      ecdhParams, privateKey, fieldSizeInBits);
+  return new Uint8Array(sharedSecret);
+}
+
+export async function generateKeyPair(
+    algorithm: 'ECDH'|'ECDSA', curve: string): Promise<CryptoKeyPair> {
+  if (algorithm != 'ECDH' && algorithm != 'ECDSA') {
+    throw new InvalidArgumentsException(
+        'algorithm must be either ECDH or ECDSA');
+  }
+  const params = {'name': algorithm, 'namedCurve': curve};
+  const ephemeralKeyPair = await window.crypto.subtle.generateKey(
+      params, /* extractable= */ true,
+      algorithm == 'ECDH' ? ['deriveKey', 'deriveBits'] : ['sign', 'verify']);
+  return ephemeralKeyPair as CryptoKeyPair;
+}
+
+export async function exportCryptoKey(cryptoKey: CryptoKey):
+    Promise<JsonWebKey> {
+  const jwk = await window.crypto.subtle.exportKey('jwk', cryptoKey);
+  return (jwk as JsonWebKey);
+}
+
+export async function importPublicKey(
+    algorithm: string, jwk: JsonWebKey): Promise<CryptoKey> {
+  if (algorithm != 'ECDH' && algorithm != 'ECDSA') {
+    throw new InvalidArgumentsException(
+        'algorithm must be either ECDH or ECDSA');
+  }
+  const {crv} = jwk;
+  if (!crv) {
+    throw new InvalidArgumentsException('crv must be provided');
+  }
+  const publicKey = await window.crypto.subtle.importKey(
+      /* format */
+      'jwk', jwk, {'name': algorithm, 'namedCurve': crv},
+      /* algorithm */
+      true,
+      /* extractable */
+      algorithm == 'ECDH' ? [] : ['verify']);
+
+  /* usage */
+  return publicKey;
+}
+
+export async function importPrivateKey(
+    algorithm: string, jwk: JsonWebKey): Promise<CryptoKey> {
+  if (algorithm != 'ECDH' && algorithm != 'ECDSA') {
+    throw new InvalidArgumentsException(
+        'algorithm must be either ECDH or ECDSA');
+  }
+  const {crv} = jwk;
+  if (!crv) {
+    throw new InvalidArgumentsException('crv must be provided');
+  }
+  const privateKey = await window.crypto.subtle.importKey(
+      /* format */
+      'jwk', jwk,
+      /* key material */
+      {'name': algorithm, 'namedCurve': crv},
+      /* algorithm */
+      true,
+      /* extractable */
+      algorithm == 'ECDH' ? ['deriveKey', 'deriveBits'] : ['sign']);
+
+  /* usage */
+  return privateKey;
+}
diff --git a/javascript/subtle/elliptic_curves_test.js b/javascript/subtle/elliptic_curves_test.js
index 30e5f98..674fa87 100644
--- a/javascript/subtle/elliptic_curves_test.js
+++ b/javascript/subtle/elliptic_curves_test.js
@@ -15,9 +15,9 @@
 goog.module('tink.subtle.EllipticCurvesTest');
 goog.setTestOnly('tink.subtle.EllipticCurvesTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const EllipticCurves = goog.require('tink.subtle.EllipticCurves');
-const Random = goog.require('tink.subtle.Random');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const EllipticCurves = goog.require('google3.third_party.tink.javascript.subtle.elliptic_curves');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
 const wycheproofEcdhTestVectors = goog.require('tink.subtle.wycheproofEcdhTestVectors');
 
 describe('elliptic curves test', function() {
@@ -55,10 +55,12 @@
 
   // Test that both ECDH public and private key are defined in the result.
   it('generate key pair e c d h', async function() {
-    const curveTypes = Object.keys(EllipticCurves.CurveType);
+    const curveTypes = [
+      EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+      EllipticCurves.CurveType.P521
+    ];
     for (let curve of curveTypes) {
-      const curveTypeString =
-          EllipticCurves.curveToString(EllipticCurves.CurveType[curve]);
+      const curveTypeString = EllipticCurves.curveToString(curve);
       const keyPair =
           await EllipticCurves.generateKeyPair('ECDH', curveTypeString);
       expect(keyPair.privateKey != null).toBe(true);
@@ -68,10 +70,12 @@
 
   // Test that both ECDSA public and private key are defined in the result.
   it('generate key pair e c d s a', async function() {
-    const curveTypes = Object.keys(EllipticCurves.CurveType);
+    const curveTypes = [
+      EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+      EllipticCurves.CurveType.P521
+    ];
     for (let curve of curveTypes) {
-      const curveTypeString =
-          EllipticCurves.curveToString(EllipticCurves.CurveType[curve]);
+      const curveTypeString = EllipticCurves.curveToString(curve);
       const keyPair =
           await EllipticCurves.generateKeyPair('ECDSA', curveTypeString);
       expect(keyPair.privateKey != null).toBe(true);
@@ -82,10 +86,12 @@
   // Test that when ECDH crypto key is exported and imported it gives the same
   // key as the original one.
   it('import export crypto key e c d h', async function() {
-    const curveTypes = Object.keys(EllipticCurves.CurveType);
+    const curveTypes = [
+      EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+      EllipticCurves.CurveType.P521
+    ];
     for (let curve of curveTypes) {
-      const curveTypeString =
-          EllipticCurves.curveToString(EllipticCurves.CurveType[curve]);
+      const curveTypeString = EllipticCurves.curveToString(curve);
       const keyPair =
           await EllipticCurves.generateKeyPair('ECDH', curveTypeString);
 
@@ -106,10 +112,12 @@
   // Test that when ECDSA crypto key is exported and imported it gives the same
   // key as the original one.
   it('import export crypto key e c d s a', async function() {
-    const curveTypes = Object.keys(EllipticCurves.CurveType);
+    const curveTypes = [
+      EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+      EllipticCurves.CurveType.P521
+    ];
     for (let curve of curveTypes) {
-      const curveTypeString =
-          EllipticCurves.curveToString(EllipticCurves.CurveType[curve]);
+      const curveTypeString = EllipticCurves.curveToString(curve);
       const keyPair =
           await EllipticCurves.generateKeyPair('ECDSA', curveTypeString);
 
@@ -131,7 +139,7 @@
   // key as the original one.
   it('import export json key e c d h', async function() {
     for (let testKey of TEST_KEYS) {
-      const jwk = /** @type{!webCrypto.JsonWebKey} */ ({
+      const jwk = /** @type {!JsonWebKey} */ ({
         'kty': 'EC',
         'crv': testKey.curve,
         'x': Bytes.toBase64(Bytes.fromHex(testKey.x), true),
@@ -158,7 +166,7 @@
   // same key as the original one.
   it('import export json key e c d s a', async function() {
     for (let testKey of TEST_KEYS) {
-      const jwk = /** @type{!webCrypto.JsonWebKey} */ ({
+      const jwk = /** @type {!JsonWebKey} */ ({
         'kty': 'EC',
         'crv': testKey.curve,
         'x': Bytes.toBase64(Bytes.fromHex(testKey.x), true),
@@ -261,9 +269,10 @@
     const point = new Uint8Array(10);
     const format = EllipticCurves.PointFormatType.UNCOMPRESSED;
 
-    for (let curve of Object.keys(EllipticCurves.CurveType)) {
-      const curveTypeString =
-          EllipticCurves.curveToString(EllipticCurves.CurveType[curve]);
+    for (let curve
+             of [EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+                 EllipticCurves.CurveType.P521]) {
+      const curveTypeString = EllipticCurves.curveToString(curve);
 
       // It should throw an exception as the point array is too short.
       try {
@@ -290,13 +299,14 @@
 
   it('point encode decode', function() {
     const format = EllipticCurves.PointFormatType.UNCOMPRESSED;
-    for (let curve of Object.keys(EllipticCurves.CurveType)) {
-      const curveType = EllipticCurves.CurveType[curve];
+    for (let curveType
+             of [EllipticCurves.CurveType.P256, EllipticCurves.CurveType.P384,
+                 EllipticCurves.CurveType.P521]) {
       const curveTypeString = EllipticCurves.curveToString(curveType);
       const x = Random.randBytes(EllipticCurves.fieldSizeInBytes(curveType));
       const y = Random.randBytes(EllipticCurves.fieldSizeInBytes(curveType));
 
-      const point = /** @type {!webCrypto.JsonWebKey} */ ({
+      const point = /** @type {!JsonWebKey} */ ({
         'kty': 'EC',
         'crv': curveTypeString,
         'x': Bytes.toBase64(x, /* websafe = */ true),
diff --git a/javascript/subtle/encrypt_then_authenticate.js b/javascript/subtle/encrypt_then_authenticate.js
deleted file mode 100644
index ac7f4c2..0000000
--- a/javascript/subtle/encrypt_then_authenticate.js
+++ /dev/null
@@ -1,136 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.EncryptThenAuthenticate');
-
-const {Aead} = goog.require('google3.third_party.tink.javascript.aead.internal.aead');
-const AesCtr = goog.require('tink.subtle.AesCtr');
-const Bytes = goog.require('tink.subtle.Bytes');
-const Hmac = goog.require('tink.subtle.Hmac');
-const IndCpaCipher = goog.require('tink.subtle.IndCpaCipher');
-const {InvalidArgumentsException} = goog.require('google3.third_party.tink.javascript.exception.invalid_arguments_exception');
-const {Mac} = goog.require('google3.third_party.tink.javascript.mac.internal.mac');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-const Validators = goog.require('tink.subtle.Validators');
-
-/**
- * This primitive performs an encrypt-then-Mac operation on plaintext and
- * additional authenticated data (aad).
- *
- * The Mac is computed over `aad || ciphertext || size of aad`, thus it
- * doesn't violate https://en.wikipedia.org/wiki/Horton_Principle.
- *
- * This implementation is based on
- * http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05.
- *
- * @public
- * @final
- */
-class EncryptThenAuthenticate extends Aead {
-  /**
-   * @param {!IndCpaCipher} cipher
-   * @param {number} ivSize the IV size in bytes
-   * @param {!Mac} mac
-   * @param {number} tagSize the MAC tag size in bytes
-   * @throws {InvalidArgumentsException}
-   */
-  constructor(cipher, ivSize, mac, tagSize) {
-    super();
-
-    /** @const @private {!IndCpaCipher} */
-    this.cipher_ = cipher;
-
-    /** @const @private {number} */
-    this.ivSize_ = ivSize;
-
-    /** @const @private {!Mac} */
-    this.mac_ = mac;
-
-    /** @const @private {number} */
-    this.tagSize_ = tagSize;
-  }
-
-  /**
-   * @param {!Uint8Array} aesKey
-   * @param {number} ivSize the size of the IV
-   * @param {string} hmacHashAlgo accepted names are SHA-1, SHA-256 and SHA-512
-   * @param {!Uint8Array} hmacKey
-   * @param {number} tagSize the size of the tag
-   * @return {!Promise.<!EncryptThenAuthenticate>}
-   * @throws {InvalidArgumentsException}
-   * @static
-   */
-  static async newAesCtrHmac(aesKey, ivSize, hmacHashAlgo, hmacKey, tagSize) {
-    Validators.requireUint8Array(aesKey);
-    Validators.requireUint8Array(hmacKey);
-
-    const cipher = await AesCtr.newInstance(aesKey, ivSize);
-    const mac = await Hmac.newInstance(hmacHashAlgo, hmacKey, tagSize);
-    return new EncryptThenAuthenticate(cipher, ivSize, mac, tagSize);
-  }
-
-  /**
-   * The plaintext is encrypted with an {@link IndCpaCipher}, then MAC
-   * is computed over `aad || ciphertext || t` where t is aad's length in bits
-   * represented as 64-bit bigendian unsigned integer. The final ciphertext
-   * format is `ind-cpa ciphertext || mac`.
-   *
-   * @override
-   */
-  async encrypt(plaintext, opt_associatedData) {
-    Validators.requireUint8Array(plaintext);
-    const payload = await this.cipher_.encrypt(plaintext);
-    let aad = new Uint8Array(0);
-    if (opt_associatedData != null) {
-      aad = opt_associatedData;
-      Validators.requireUint8Array(opt_associatedData);
-    }
-    const aadLength = Bytes.fromNumber(aad.length * 8);
-    const mac =
-        await this.mac_.computeMac(Bytes.concat(aad, payload, aadLength));
-    if (this.tagSize_ != mac.length) {
-      throw new SecurityException(
-          'invalid tag size, expected ' + this.tagSize_ + ' but got ' +
-          mac.length);
-    }
-    return Bytes.concat(payload, mac);
-  }
-
-  /**
-   * @override
-   */
-  async decrypt(ciphertext, opt_associatedData) {
-    Validators.requireUint8Array(ciphertext);
-    if (ciphertext.length < this.ivSize_ + this.tagSize_) {
-      throw new SecurityException('ciphertext too short');
-    }
-    const payload = new Uint8Array(
-        ciphertext.subarray(0, ciphertext.length - this.tagSize_));
-    let aad = new Uint8Array(0);
-    if (opt_associatedData != null) {
-      aad = opt_associatedData;
-      Validators.requireUint8Array(opt_associatedData);
-    }
-    const aadLength = Bytes.fromNumber(aad.length * 8);
-    const input = Bytes.concat(aad, payload, aadLength);
-    const tag = new Uint8Array(ciphertext.subarray(payload.length));
-    const isValidMac = await this.mac_.verifyMac(tag, input);
-    if (!isValidMac) {
-      throw new SecurityException('invalid MAC');
-    }
-    return await this.cipher_.decrypt(payload);
-  }
-}
-
-exports = EncryptThenAuthenticate;
diff --git a/javascript/subtle/encrypt_then_authenticate.ts b/javascript/subtle/encrypt_then_authenticate.ts
new file mode 100644
index 0000000..f2392f2
--- /dev/null
+++ b/javascript/subtle/encrypt_then_authenticate.ts
@@ -0,0 +1,105 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {Aead} from '../aead/internal/aead';
+import {SecurityException} from '../exception/security_exception';
+import {Mac} from '../mac/internal/mac';
+
+import * as aesCtr from './aes_ctr';
+import * as Bytes from './bytes';
+import * as hmac from './hmac';
+import {IndCpaCipher} from './ind_cpa_cipher';
+import * as Validators from './validators';
+
+/**
+ * This primitive performs an encrypt-then-Mac operation on plaintext and
+ * additional authenticated data (aad).
+ *
+ * The Mac is computed over `aad || ciphertext || size of aad`, thus it
+ * doesn't violate https://en.wikipedia.org/wiki/Horton_Principle.
+ *
+ * This implementation is based on
+ * http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05.
+ *
+ * @final
+ */
+export class EncryptThenAuthenticate implements Aead {
+  /**
+   * @param ivSize the IV size in bytes
+   * @param tagSize the MAC tag size in bytes
+   * @throws {InvalidArgumentsException}
+   */
+  constructor(
+      private readonly cipher: IndCpaCipher, private readonly ivSize: number,
+      private readonly mac: Mac, private readonly tagSize: number) {}
+
+  /**
+   * The plaintext is encrypted with an {@link IndCpaCipher}, then MAC
+   * is computed over `aad || ciphertext || t` where t is aad's length in bits
+   * represented as 64-bit bigendian unsigned integer. The final ciphertext
+   * format is `ind-cpa ciphertext || mac`.
+   *
+   * @override
+   */
+  async encrypt(plaintext: Uint8Array, associatedData = new Uint8Array(0)):
+      Promise<Uint8Array> {
+    Validators.requireUint8Array(plaintext);
+    const payload = await this.cipher.encrypt(plaintext);
+    Validators.requireUint8Array(associatedData);
+    const aadLength = Bytes.fromNumber(associatedData.length * 8);
+    const mac = await this.mac.computeMac(
+        Bytes.concat(associatedData, payload, aadLength));
+    if (this.tagSize != mac.length) {
+      throw new SecurityException(
+          'invalid tag size, expected ' + this.tagSize + ' but got ' +
+          mac.length);
+    }
+    return Bytes.concat(payload, mac);
+  }
+
+  /**
+   * @override
+   */
+  async decrypt(ciphertext: Uint8Array, associatedData = new Uint8Array(0)):
+      Promise<Uint8Array> {
+    Validators.requireUint8Array(ciphertext);
+    if (ciphertext.length < this.ivSize + this.tagSize) {
+      throw new SecurityException('ciphertext too short');
+    }
+    const payload = new Uint8Array(
+        ciphertext.subarray(0, ciphertext.length - this.tagSize));
+    Validators.requireUint8Array(associatedData);
+    const aadLength = Bytes.fromNumber(associatedData.length * 8);
+    const input = Bytes.concat(associatedData, payload, aadLength);
+    const tag = new Uint8Array(ciphertext.subarray(payload.length));
+    const isValidMac = await this.mac.verifyMac(tag, input);
+    if (!isValidMac) {
+      throw new SecurityException('invalid MAC');
+    }
+    return this.cipher.decrypt(payload);
+  }
+}
+
+/**
+ * @param ivSize the size of the IV
+ * @param hmacHashAlgo accepted names are SHA-1, SHA-256 and SHA-512
+ * @param tagSize the size of the tag
+ * @throws {InvalidArgumentsException}
+ * @static
+ */
+export async function aesCtrHmacFromRawKeys(
+    aesKey: Uint8Array, ivSize: number, hmacHashAlgo: string,
+    hmacKey: Uint8Array, tagSize: number): Promise<EncryptThenAuthenticate> {
+  Validators.requireUint8Array(aesKey);
+  Validators.requireUint8Array(hmacKey);
+  const cipher = await aesCtr.fromRawKey(aesKey, ivSize);
+  const mac = await hmac.fromRawKey(hmacHashAlgo, hmacKey, tagSize);
+  return new EncryptThenAuthenticate(cipher, ivSize, mac, tagSize);
+}
diff --git a/javascript/subtle/encrypt_then_authenticate_test.js b/javascript/subtle/encrypt_then_authenticate_test.js
index c8e8d79..0519f1f 100644
--- a/javascript/subtle/encrypt_then_authenticate_test.js
+++ b/javascript/subtle/encrypt_then_authenticate_test.js
@@ -15,9 +15,9 @@
 goog.module('tink.subtle.EncryptThenAuthenticateTest');
 goog.setTestOnly('tink.subtle.EncryptThenAuthenticateTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const EncryptThenAuthenticate = goog.require('tink.subtle.EncryptThenAuthenticate');
-const Random = goog.require('tink.subtle.Random');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const {aesCtrHmacFromRawKeys} = goog.require('google3.third_party.tink.javascript.subtle.encrypt_then_authenticate');
 
 describe('encrypt then authenticate test', function() {
   beforeEach(function() {
@@ -31,7 +31,7 @@
   });
 
   it('basic', async function() {
-    const aead = await EncryptThenAuthenticate.newAesCtrHmac(
+    const aead = await aesCtrHmacFromRawKeys(
         Random.randBytes(16) /* aesKey */, 12 /* ivSize */, 'SHA-256',
         Random.randBytes(16) /* hmacKey */, 10 /* tagSize */);
     for (let i = 0; i < 100; i++) {
@@ -40,12 +40,11 @@
       let plaintext = await aead.decrypt(ciphertext);
       expect(Bytes.toHex(plaintext)).toBe(Bytes.toHex(msg));
 
-      let aad = null;
-      ciphertext = await aead.encrypt(msg, aad);
-      plaintext = await aead.decrypt(ciphertext, aad);
+      ciphertext = await aead.encrypt(msg);
+      plaintext = await aead.decrypt(ciphertext);
       expect(Bytes.toHex(plaintext)).toBe(Bytes.toHex(msg));
 
-      aad = Random.randBytes(20);
+      const aad = Random.randBytes(20);
       ciphertext = await aead.encrypt(msg, aad);
       plaintext = await aead.decrypt(ciphertext, aad);
       expect(Bytes.toHex(plaintext)).toBe(Bytes.toHex(msg));
@@ -53,7 +52,7 @@
   });
 
   it('probabilistic encryption', async function() {
-    const aead = await EncryptThenAuthenticate.newAesCtrHmac(
+    const aead = await aesCtrHmacFromRawKeys(
         Random.randBytes(16) /* aesKey */, 12 /* ivSize */, 'SHA-256',
         Random.randBytes(16) /* hmacKey */, 10 /* tagSize */);
     const msg = Random.randBytes(20);
@@ -67,7 +66,7 @@
   });
 
   it('bit flip ciphertext', async function() {
-    const aead = await EncryptThenAuthenticate.newAesCtrHmac(
+    const aead = await aesCtrHmacFromRawKeys(
         Random.randBytes(16) /* aesKey */, 16 /* ivSize */, 'SHA-256',
         Random.randBytes(16) /* hmacKey */, 16 /* tagSize */);
     const plaintext = Random.randBytes(8);
@@ -88,7 +87,7 @@
   });
 
   it('bit flip aad', async function() {
-    const aead = await EncryptThenAuthenticate.newAesCtrHmac(
+    const aead = await aesCtrHmacFromRawKeys(
         Random.randBytes(16) /* aesKey */, 16 /* ivSize */, 'SHA-256',
         Random.randBytes(16) /* hmacKey */, 16 /* tagSize */);
     const plaintext = Random.randBytes(8);
@@ -109,7 +108,7 @@
   });
 
   it('truncation', async function() {
-    const aead = await EncryptThenAuthenticate.newAesCtrHmac(
+    const aead = await aesCtrHmacFromRawKeys(
         Random.randBytes(16) /* aesKey */, 16 /* ivSize */, 'SHA-256',
         Random.randBytes(16) /* hmacKey */, 16 /* tagSize */);
     const plaintext = Random.randBytes(8);
@@ -184,7 +183,7 @@
     ];
     for (let i = 0; i < RFC_TEST_VECTORS.length; i++) {
       const testVector = RFC_TEST_VECTORS[i];
-      const aead = await EncryptThenAuthenticate.newAesCtrHmac(
+      const aead = await aesCtrHmacFromRawKeys(
           Bytes.fromHex(testVector['encryptionKey']), testVector['ivSize'],
           testVector['hashAlgoName'], Bytes.fromHex(testVector['macKey']),
           testVector['tagSize']);
diff --git a/javascript/subtle/hkdf.js b/javascript/subtle/hkdf.ts
similarity index 70%
rename from javascript/subtle/hkdf.js
rename to javascript/subtle/hkdf.ts
index a50831c..5313dd7 100644
--- a/javascript/subtle/hkdf.js
+++ b/javascript/subtle/hkdf.ts
@@ -1,43 +1,40 @@
 // 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.
-//
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
  * @fileoverview An implementation of HKDF, RFC 5869.
  */
+import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
 
-goog.module('tink.subtle.Hkdf');
-
-const Hmac = goog.require('tink.subtle.Hmac');
-const {InvalidArgumentsException} = goog.require('google3.third_party.tink.javascript.exception.invalid_arguments_exception');
-const Validators = goog.require('tink.subtle.Validators');
+import {fromRawKey as hmacFromRawKey} from './hmac';
+import * as Validators from './validators';
 
 /**
  * Computes an HKDF.
  *
- * @param {number} size The length of the generated pseudorandom string in
+ * @param size The length of the generated pseudorandom string in
  *     bytes. The maximal size is 255 * DigestSize, where DigestSize is the size
  *     of the underlying HMAC.
- * @param {string} hash the name of the hash function. Accepted names are SHA-1,
+ * @param hash the name of the hash function. Accepted names are SHA-1,
  *     SHA-256 and SHA-512
- * @param {!Uint8Array} ikm Input keying material.
- * @param {!Uint8Array} info Context and application specific
+ * @param ikm Input keying material.
+ * @param info Context and application specific
  *     information (can be a zero-length array).
- * @param {!Uint8Array=} opt_salt Salt value (a non-secret random
+ * @param opt_salt Salt value (a non-secret random
  *     value). If not provided, it is set to a string of hash length zeros.
- * @return {!Promise.<!Uint8Array>} Output keying material (okm).
+ * @return Output keying material (okm).
  */
-const compute = async function(size, hash, ikm, info, opt_salt) {
+export async function compute(
+    size: number, hash: string, ikm: Uint8Array, info: Uint8Array,
+    opt_salt?: Uint8Array): Promise<Uint8Array> {
   let digestSize;
   if (!Number.isInteger(size)) {
     throw new InvalidArgumentsException('size must be an integer');
@@ -67,10 +64,8 @@
     default:
       throw new InvalidArgumentsException(hash + ' is not supported');
   }
-
   Validators.requireUint8Array(ikm);
   Validators.requireUint8Array(info);
-
   let salt = opt_salt;
   if (opt_salt == null || salt === undefined || salt.length == 0) {
     salt = new Uint8Array(digestSize);
@@ -78,17 +73,19 @@
   Validators.requireUint8Array(salt);
 
   // Extract.
-  let hmac = await Hmac.newInstance(hash, salt, digestSize);
-  const prk = await hmac.computeMac(ikm);  // Pseudorandom Key
+  let hmac = await hmacFromRawKey(hash, salt, digestSize);
+  const prk = await hmac.computeMac(
+      // Pseudorandom Key
+      ikm);
 
   // Expand
-  hmac = await Hmac.newInstance(hash, prk, digestSize);
+  hmac = await hmacFromRawKey(hash, prk, digestSize);
   let ctr = 1;
   let pos = 0;
   let digest = new Uint8Array(0);
-  let result = new Uint8Array(size);
+  const result = new Uint8Array(size);
   while (true) {
-    let input = new Uint8Array(digest.length + info.length + 1);
+    const input = new Uint8Array(digest.length + info.length + 1);
     input.set(digest, 0);
     input.set(info, digest.length);
     input[input.length - 1] = ctr;
@@ -103,8 +100,4 @@
     }
   }
   return result;
-};
-
-exports = {
-  compute,
-};
+}
diff --git a/javascript/subtle/hkdf_test.js b/javascript/subtle/hkdf_test.js
index 8e98007..8dd443d 100644
--- a/javascript/subtle/hkdf_test.js
+++ b/javascript/subtle/hkdf_test.js
@@ -15,9 +15,9 @@
 goog.module('tink.subtle.HkdfTest');
 goog.setTestOnly('tink.subtle.HkdfTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const Hkdf = goog.require('tink.subtle.Hkdf');
-const Random = goog.require('tink.subtle.Random');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const Hkdf = goog.require('google3.third_party.tink.javascript.subtle.hkdf');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
 
 describe('hkdf test', function() {
   it('constructor', async function() {
diff --git a/javascript/subtle/hmac.js b/javascript/subtle/hmac.js
deleted file mode 100644
index 42a8e66..0000000
--- a/javascript/subtle/hmac.js
+++ /dev/null
@@ -1,125 +0,0 @@
-// 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.
-//
-////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.Hmac');
-
-const Bytes = goog.require('tink.subtle.Bytes');
-const {InvalidArgumentsException} = goog.require('google3.third_party.tink.javascript.exception.invalid_arguments_exception');
-const {Mac} = goog.require('google3.third_party.tink.javascript.mac.internal.mac');
-const Validators = goog.require('tink.subtle.Validators');
-
-/**
- * The minimum tag size.
- *
- * @const {number}
- */
-const MIN_TAG_SIZE_IN_BYTES = 10;
-
-/**
- * Implementation of HMAC.
- *
- * @public
- * @final
- */
-class Hmac extends Mac {
-  /**
-   * @param {string} hash accepted names are SHA-1, SHA-256 and SHA-512
-   * @param {!webCrypto.CryptoKey} key
-   * @param {number} tagSize the size of the tag
-   */
-  constructor(hash, key, tagSize) {
-    super();
-
-    /** @const @private {string} */
-    this.hash_ = hash;
-
-    /** @const @private {number} */
-    this.tagSize_ = tagSize;
-
-    /** @const @private {!webCrypto.CryptoKey} */
-    this.key_ = key;
-  }
-
-  /**
-   * @param {string} hash accepted names are SHA-1, SHA-256 and SHA-512
-   * @param {!Uint8Array} key
-   * @param {number} tagSize the size of the tag
-   * @return {!Promise.<!Mac>}
-   * @static
-   */
-  static async newInstance(hash, key, tagSize) {
-    Validators.requireUint8Array(key);
-    if (!Number.isInteger(tagSize)) {
-      throw new InvalidArgumentsException(
-          'invalid tag size, must be an integer');
-    }
-    if (tagSize < MIN_TAG_SIZE_IN_BYTES) {
-      throw new InvalidArgumentsException(
-          'tag too short, must be at least ' + MIN_TAG_SIZE_IN_BYTES +
-          ' bytes');
-    }
-    switch (hash) {
-      case 'SHA-1':
-        if (tagSize > 20) {
-          throw new InvalidArgumentsException(
-              'tag too long, must not be larger than 20 bytes');
-        }
-        break;
-      case 'SHA-256':
-        if (tagSize > 32) {
-          throw new InvalidArgumentsException(
-              'tag too long, must not be larger than 32 bytes');
-        }
-        break;
-      case 'SHA-512':
-        if (tagSize > 64) {
-          throw new InvalidArgumentsException(
-              'tag too long, must not be larger than 64 bytes');
-        }
-        break;
-      default:
-        throw new InvalidArgumentsException(hash + ' is not supported');
-    }
-
-    // TODO(b/115974209): Add check that key.length > 16.
-
-    const cryptoKey = await self.crypto.subtle.importKey(
-        'raw', key,
-        {'name': 'HMAC', 'hash': {'name': hash}, 'length': key.length * 8},
-        false, ['sign', 'verify']);
-    return new Hmac(hash, cryptoKey, tagSize);
-  }
-
-  /**
-   * @override
-   */
-  async computeMac(data) {
-    Validators.requireUint8Array(data);
-    const tag = await self.crypto.subtle.sign(
-        {'name': 'HMAC', 'hash': {'name': this.hash_}}, this.key_, data);
-    return new Uint8Array(tag.slice(0, this.tagSize_));
-  }
-
-  /**
-   * @override
-   */
-  async verifyMac(tag, data) {
-    Validators.requireUint8Array(tag);
-    Validators.requireUint8Array(data);
-    const computedTag = await this.computeMac(data);
-    return Bytes.isEqual(tag, computedTag);
-  }
-}
-
-exports = Hmac;
diff --git a/javascript/subtle/hmac.ts b/javascript/subtle/hmac.ts
new file mode 100644
index 0000000..5452075
--- /dev/null
+++ b/javascript/subtle/hmac.ts
@@ -0,0 +1,101 @@
+// 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.
+////////////////////////////////////////////////////////////////////////////////
+import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
+import {Mac} from '../mac/internal/mac';
+
+import * as Bytes from './bytes';
+import * as Validators from './validators';
+
+/**
+ * The minimum tag size.
+ *
+ */
+const MIN_TAG_SIZE_IN_BYTES: number = 10;
+
+/**
+ * Implementation of HMAC.
+ *
+ * @final
+ */
+export class Hmac implements Mac {
+  /**
+   * @param hash accepted names are SHA-1, SHA-256 and SHA-512
+   * @param tagSize the size of the tag
+   */
+  constructor(
+      private readonly hash: string, private readonly key: CryptoKey,
+      private readonly tagSize: number) {}
+
+  /**
+   * @override
+   */
+  async computeMac(data: Uint8Array): Promise<Uint8Array> {
+    Validators.requireUint8Array(data);
+    const tag = await self.crypto.subtle.sign(
+        {'name': 'HMAC', 'hash': {'name': this.hash}}, this.key, data);
+    return new Uint8Array(tag.slice(0, this.tagSize));
+  }
+
+  /**
+   * @override
+   */
+  async verifyMac(tag: Uint8Array, data: Uint8Array): Promise<boolean> {
+    Validators.requireUint8Array(tag);
+    Validators.requireUint8Array(data);
+    const computedTag = await this.computeMac(data);
+    return Bytes.isEqual(tag, computedTag);
+  }
+}
+
+/**
+ * @param hash accepted names are SHA-1, SHA-256 and SHA-512
+ * @param tagSize the size of the tag
+ */
+export async function fromRawKey(
+    hash: string, key: Uint8Array, tagSize: number): Promise<Mac> {
+  Validators.requireUint8Array(key);
+  if (!Number.isInteger(tagSize)) {
+    throw new InvalidArgumentsException('invalid tag size, must be an integer');
+  }
+  if (tagSize < MIN_TAG_SIZE_IN_BYTES) {
+    throw new InvalidArgumentsException(
+        'tag too short, must be at least ' + MIN_TAG_SIZE_IN_BYTES + ' bytes');
+  }
+  switch (hash) {
+    case 'SHA-1':
+      if (tagSize > 20) {
+        throw new InvalidArgumentsException(
+            'tag too long, must not be larger than 20 bytes');
+      }
+      break;
+    case 'SHA-256':
+      if (tagSize > 32) {
+        throw new InvalidArgumentsException(
+            'tag too long, must not be larger than 32 bytes');
+      }
+      break;
+    case 'SHA-512':
+      if (tagSize > 64) {
+        throw new InvalidArgumentsException(
+            'tag too long, must not be larger than 64 bytes');
+      }
+      break;
+    default:
+      throw new InvalidArgumentsException(hash + ' is not supported');
+  }
+
+  // TODO(b/115974209): Add check that key.length > 16.
+  const cryptoKey = await self.crypto.subtle.importKey(
+      'raw', key,
+      {'name': 'HMAC', 'hash': {'name': hash}, 'length': key.length * 8}, false,
+      ['sign', 'verify']);
+  return new Hmac(hash, cryptoKey, tagSize);
+}
diff --git a/javascript/subtle/hmac_test.js b/javascript/subtle/hmac_test.js
index f391652..f489fd1 100644
--- a/javascript/subtle/hmac_test.js
+++ b/javascript/subtle/hmac_test.js
@@ -15,15 +15,15 @@
 goog.module('tink.subtle.HmacTest');
 goog.setTestOnly('tink.subtle.HmacTest');
 
-const Bytes = goog.require('tink.subtle.Bytes');
-const Hmac = goog.require('tink.subtle.Hmac');
-const Random = goog.require('tink.subtle.Random');
+const Bytes = goog.require('google3.third_party.tink.javascript.subtle.bytes');
+const Random = goog.require('google3.third_party.tink.javascript.subtle.random');
+const {fromRawKey: hmacFromRawKey} = goog.require('google3.third_party.tink.javascript.subtle.hmac');
 
 describe('hmac test', function() {
   it('basic', async function() {
     const key = Random.randBytes(16);
     const msg = Random.randBytes(4);
-    const hmac = await Hmac.newInstance('SHA-1', key, 10);
+    const hmac = await hmacFromRawKey('SHA-1', key, 10);
     const tag = await hmac.computeMac(msg);
     expect(tag.length).toBe(10);
     expect(await hmac.verifyMac(tag, msg)).toBe(true);
@@ -31,7 +31,7 @@
 
   it('constructor', async function() {
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'blah', Random.randBytes(16), 16);  // invalid HMAC algo name
       fail('Should throw an exception.');
     } catch (e) {
@@ -40,7 +40,7 @@
     }
 
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'SHA-1', Random.randBytes(15), 16);  // invalid key size
       // TODO(b/115974209): This case does not throw an exception.
     } catch (e) {
@@ -49,7 +49,7 @@
     }
 
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'SHA-1', Random.randBytes(16), 9);  // tag size too short
       fail('Should throw an exception.');
     } catch (e) {
@@ -58,7 +58,7 @@
               'InvalidArgumentsException: tag too short, must be at least 10 bytes');
     }
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'SHA-1', Random.randBytes(16), 21);  // tag size too long
       fail('Should throw an exception.');
     } catch (e) {
@@ -68,7 +68,7 @@
     }
 
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'SHA-256', Random.randBytes(15), 16);  // invalid key size
       // TODO(b/115974209): This case does not throw an exception.
     } catch (e) {
@@ -77,7 +77,7 @@
     }
 
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'SHA-256', Random.randBytes(16), 9);  // tag size too short
       fail('Should throw an exception.');
     } catch (e) {
@@ -87,7 +87,7 @@
     }
 
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'SHA-256', Random.randBytes(16), 33);  // tag size too long
       fail('Should throw an exception.');
     } catch (e) {
@@ -97,7 +97,7 @@
     }
 
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'SHA-512', Random.randBytes(15), 16);  // invalid key size
       // TODO(b/115974209): This case does not throw an exception.
     } catch (e) {
@@ -106,7 +106,7 @@
     }
 
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'SHA-512', Random.randBytes(16), 9);  // tag size too short
       fail('Should throw an exception.');
     } catch (e) {
@@ -116,7 +116,7 @@
     }
 
     try {
-      await Hmac.newInstance(
+      await hmacFromRawKey(
           'SHA-512', Random.randBytes(16), 65);  // tag size too long
       fail('Should throw an exception.');
     } catch (e) {
@@ -128,7 +128,7 @@
 
   it('constructor, invalid tag sizes', async function() {
     try {
-      await Hmac.newInstance('SHA-512', Random.randBytes(16), NaN);
+      await hmacFromRawKey('SHA-512', Random.randBytes(16), NaN);
       fail('Should throw an exception.');
     } catch (e) {
       expect(e.toString())
@@ -137,7 +137,7 @@
     }
 
     try {
-      await Hmac.newInstance('SHA-512', Random.randBytes(16), 12.5);
+      await hmacFromRawKey('SHA-512', Random.randBytes(16), 12.5);
       fail('Should throw an exception.');
     } catch (e) {
       expect(e.toString())
@@ -149,7 +149,7 @@
   it('modify', async function() {
     const key = Random.randBytes(16);
     const msg = Random.randBytes(8);
-    const hmac = await Hmac.newInstance('SHA-1', key, 20);
+    const hmac = await hmacFromRawKey('SHA-1', key, 20);
     const tag = await hmac.computeMac(msg);
 
     // Modify tag.
@@ -216,7 +216,7 @@
       const key = Bytes.fromHex(testVector['key']);
       const message = Bytes.fromHex(testVector['message']);
       const tag = Bytes.fromHex(testVector['tag']);
-      const hmac = await Hmac.newInstance(testVector['algo'], key, tag.length);
+      const hmac = await hmacFromRawKey(testVector['algo'], key, tag.length);
       expect(await hmac.verifyMac(tag, message)).toBe(true);
     }
   });
diff --git a/javascript/subtle/ind_cpa_cipher.js b/javascript/subtle/ind_cpa_cipher.ts
similarity index 68%
rename from javascript/subtle/ind_cpa_cipher.js
rename to javascript/subtle/ind_cpa_cipher.ts
index a0dd0ca..55961ab 100644
--- a/javascript/subtle/ind_cpa_cipher.js
+++ b/javascript/subtle/ind_cpa_cipher.ts
@@ -1,21 +1,14 @@
 // 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.
-//
 ////////////////////////////////////////////////////////////////////////////////
 
-goog.module('tink.subtle.IndCpaCipher');
-
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-
 /**
  * Interface for symmetric key ciphers that are indistinguishable against
  * chosen-plaintext attacks.
@@ -24,29 +17,25 @@
  * authentication, thus should not be used directly, but only to construct safer
  * primitives such as {@link tink.Aead}.
  *
- * @protected
- * @record
  */
-class IndCpaCipher {
+export interface IndCpaCipher {
   /**
    * Encrypts `plaintext`.
    *
-   * @param {!Uint8Array} plaintext the plaintext to be encrypted. It must be
+   * @param plaintext the plaintext to be encrypted. It must be
    *     non-null, but can also be an empty (zero-length) byte array.
-   * @return {!Promise.<!Uint8Array>} resulting ciphertext
+   * @return resulting ciphertext
    * @throws {SecurityException}
    */
-  encrypt(plaintext) {}
+  encrypt(plaintext: Uint8Array): Promise<Uint8Array>;
 
   /**
    * Decrypts ciphertext with associated authenticated data.
    *
-   * @param {!Uint8Array} ciphertext the ciphertext to be decrypted, must be
+   * @param ciphertext the ciphertext to be decrypted, must be
    *     non-null.
-   * @return {!Promise.<!Uint8Array>} resulting plaintext
+   * @return resulting plaintext
    * @throws {SecurityException}
    */
-  decrypt(ciphertext) {}
+  decrypt(ciphertext: Uint8Array): Promise<Uint8Array>;
 }
-
-exports = IndCpaCipher;
diff --git a/javascript/subtle/random.js b/javascript/subtle/random.ts
similarity index 74%
rename from javascript/subtle/random.js
rename to javascript/subtle/random.ts
index ae478d8..ecd890c 100644
--- a/javascript/subtle/random.js
+++ b/javascript/subtle/random.ts
@@ -1,40 +1,31 @@
 // 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.
-//
 ////////////////////////////////////////////////////////////////////////////////
 
 /**
  * @fileoverview Several simple wrappers of crypto.getRandomValues.
- * @public
  */
-
-goog.module('tink.subtle.Random');
-
-const {InvalidArgumentsException} = goog.require('google3.third_party.tink.javascript.exception.invalid_arguments_exception');
+import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
 
 /**
  * Randomly generates `n` bytes.
  *
- * @param {number} n number of bytes to generate
- * @return {!Uint8Array} the random bytes
+ * @param n number of bytes to generate
+ * @return the random bytes
  * @static
  */
-const randBytes = function(n) {
+export function randBytes(n: number): Uint8Array {
   if (!Number.isInteger(n) || n < 0) {
     throw new InvalidArgumentsException('n must be a nonnegative integer');
   }
   const result = new Uint8Array(n);
   crypto.getRandomValues(result);
   return result;
-};
-
-exports = {randBytes};
+}
diff --git a/javascript/subtle/validators.js b/javascript/subtle/validators.ts
similarity index 71%
rename from javascript/subtle/validators.js
rename to javascript/subtle/validators.ts
index 76eac26..dd0c161 100644
--- a/javascript/subtle/validators.js
+++ b/javascript/subtle/validators.ts
@@ -1,79 +1,66 @@
 // 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.
-//
 ////////////////////////////////////////////////////////////////////////////////
-
-goog.module('tink.subtle.Validators');
-
-const {InvalidArgumentsException} = goog.require('google3.third_party.tink.javascript.exception.invalid_arguments_exception');
-const {SecurityException} = goog.require('google3.third_party.tink.javascript.exception.security_exception');
-
-/**
- * @const @public {!Array.<number>}
- */
-const SUPPORTED_AES_KEY_SIZES = [16, 32];
+import {InvalidArgumentsException} from '../exception/invalid_arguments_exception';
+import {SecurityException} from '../exception/security_exception';
+const SUPPORTED_AES_KEY_SIZES: number[] = [16, 32];
 
 /**
  * Validates AES key sizes, at the moment only 128-bit and 256-bit keys are
  * supported.
  *
- * @param {number} n the key size in bytes
+ * @param n the key size in bytes
  * @throws {!InvalidArgumentsException}
  * @static
  */
-const validateAesKeySize = function(n) {
+export function validateAesKeySize(n: number) {
   if (!SUPPORTED_AES_KEY_SIZES.includes(n)) {
     throw new InvalidArgumentsException('unsupported AES key size: ' + n);
   }
-};
+}
 
 /**
  * Validates that the input is a non null Uint8Array.
  *
- * @param {!Uint8Array} input
  * @throws {!InvalidArgumentsException}
  * @static
  */
-const requireUint8Array = function(input) {
+export function requireUint8Array(input: Uint8Array) {
   if (input == null || !(input instanceof Uint8Array)) {
     throw new InvalidArgumentsException('input must be a non null Uint8Array');
   }
-};
+}
 
 /**
  * Validates version, throws exception if candidate version is negative or
  * bigger than expected.
  *
- * @param {number} candidate - version to be validated
- * @param {number} maxVersion - upper bound on version
+ * @param candidate - version to be validated
+ * @param maxVersion - upper bound on version
  * @throws {!SecurityException}
  * @static
  */
-const validateVersion = function(candidate, maxVersion) {
+export function validateVersion(candidate: number, maxVersion: number) {
   if (candidate < 0 || candidate > maxVersion) {
     throw new SecurityException(
         'Version is out of bound, must be ' +
         'between 0 and ' + maxVersion + '.');
   }
-};
+}
 
 /**
  * Validates ECDSA parameters.
  *
- * @param {string} curve
- * @param {string} hash
  * @throws {!SecurityException}
  */
-const validateEcdsaParams = function(curve, hash) {
+export function validateEcdsaParams(curve: string, hash: string) {
   switch (curve) {
     case 'P-256':
       if (hash != 'SHA-256') {
@@ -97,11 +84,4 @@
     default:
       throw new SecurityException('unsupported curve: ' + curve);
   }
-};
-
-exports = {
-  validateAesKeySize,
-  validateEcdsaParams,
-  requireUint8Array,
-  validateVersion
-};
+}
diff --git a/javascript/testing/index.ts b/javascript/testing/index.ts
index 5cca6a8..a92ac5c 100644
--- a/javascript/testing/index.ts
+++ b/javascript/testing/index.ts
@@ -1,3 +1,3 @@
-import Registry from 'goog:tink.Registry'; // from //third_party/tink/javascript:registry_legacy
+import * as Registry from '../internal/registry';
 
 export const resetRegistry = Registry.reset;
diff --git a/kokoro/continuous.sh b/kokoro/continuous.sh
index 4fcaca0..4c93061 100755
--- a/kokoro/continuous.sh
+++ b/kokoro/continuous.sh
@@ -26,9 +26,6 @@
 
 source ./kokoro/run_tests.sh
 
-# Test that Tink can be installed with the standard Go tooling.
-go get github.com/google/tink/go/...
-
 # Run all manual tests.
 (
   cd java_src
diff --git a/kokoro/gcp_ubuntu_per_language/cross_language/common.cfg b/kokoro/gcp_ubuntu_per_language/cross_language/common.cfg
new file mode 100644
index 0000000..9bae2a8
--- /dev/null
+++ b/kokoro/gcp_ubuntu_per_language/cross_language/common.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "tink/kokoro/gcp_ubuntu_per_language/cross_language/run_tests.sh"
diff --git a/kokoro/gcp_ubuntu_per_language/go/continuous.cfg b/kokoro/gcp_ubuntu_per_language/cross_language/continuous.cfg
similarity index 100%
copy from kokoro/gcp_ubuntu_per_language/go/continuous.cfg
copy to kokoro/gcp_ubuntu_per_language/cross_language/continuous.cfg
diff --git a/kokoro/gcp_ubuntu_per_language/go/presubmit.cfg b/kokoro/gcp_ubuntu_per_language/cross_language/presubmit.cfg
similarity index 100%
copy from kokoro/gcp_ubuntu_per_language/go/presubmit.cfg
copy to kokoro/gcp_ubuntu_per_language/cross_language/presubmit.cfg
diff --git a/kokoro/gcp_ubuntu_per_language/cross_language/run_tests.sh b/kokoro/gcp_ubuntu_per_language/cross_language/run_tests.sh
new file mode 100644
index 0000000..0a23bd1
--- /dev/null
+++ b/kokoro/gcp_ubuntu_per_language/cross_language/run_tests.sh
@@ -0,0 +1,73 @@
+#!/bin/bash
+
+set -euo pipefail
+
+CURRENT_BAZEL_VERSION=""
+
+install_python3() {
+  : "${PYTHON_VERSION:=3.7.1}"
+
+  # Update python version list.
+  (
+    cd /home/kbuilder/.pyenv/plugins/python-build/../..
+    git pull
+  )
+  # Install Python.
+  eval "$(pyenv init -)"
+  pyenv install -v "${PYTHON_VERSION}"
+  pyenv global "${PYTHON_VERSION}"
+}
+
+use_bazel() {
+  local candidate_version="$1"
+  if [[ "${candidate_version}" != "${CURRENT_BAZEL_VERSION}" ]]; then
+    CURRENT_BAZEL_VERSION="${candidate_version}"
+    if [[ -n "${KOKORO_ROOT:-}" ]] ; then
+      use_bazel.sh "${candidate_version}"
+    else
+      bazel --version
+    fi
+  fi
+}
+
+main() {
+  if [[ -n "${KOKORO_ROOT:-}" ]] ; then
+    install_python3
+    cd "${KOKORO_ARTIFACTS_DIR}/git/tink"
+  fi
+  (
+    cd testing/cc
+    use_bazel "$(cat .bazelversion)"
+    time bazel build -- ...
+    time bazel test --test_output=errors -- ...
+  )
+  (
+    cd testing/go
+    use_bazel "$(cat .bazelversion)"
+    time bazel build -- ...
+    time bazel test --test_output=errors -- ...
+  )
+  (
+    cd testing/java_src
+    use_bazel "$(cat .bazelversion)"
+    time bazel build -- ...
+    time bazel build :testing_server_deploy.jar
+    time bazel test --test_output=errors -- ...
+  )
+  (
+    cd testing/python
+    use_bazel "$(cat .bazelversion)"
+    time bazel build -- ...
+    time bazel test --test_output=errors -- ...
+  )
+
+  local testing_dir="${PWD}/testing"
+  (
+    cd testing/cross_language
+    use_bazel "$(cat .bazelversion)"
+    time bazel test \
+      --test_env testing_dir="${testing_dir}" --test_output=errors -- ...
+  )
+}
+
+main "$@"
diff --git a/kokoro/gcp_ubuntu_per_language/go/bazel/common.cfg b/kokoro/gcp_ubuntu_per_language/go/bazel/common.cfg
new file mode 100644
index 0000000..f8f42eb
--- /dev/null
+++ b/kokoro/gcp_ubuntu_per_language/go/bazel/common.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "tink/kokoro/gcp_ubuntu_per_language/go/bazel/run_tests.sh"
diff --git a/kokoro/gcp_ubuntu_per_language/go/continuous.cfg b/kokoro/gcp_ubuntu_per_language/go/bazel/continuous.cfg
similarity index 100%
rename from kokoro/gcp_ubuntu_per_language/go/continuous.cfg
rename to kokoro/gcp_ubuntu_per_language/go/bazel/continuous.cfg
diff --git a/kokoro/gcp_ubuntu_per_language/go/presubmit.cfg b/kokoro/gcp_ubuntu_per_language/go/bazel/presubmit.cfg
similarity index 100%
rename from kokoro/gcp_ubuntu_per_language/go/presubmit.cfg
rename to kokoro/gcp_ubuntu_per_language/go/bazel/presubmit.cfg
diff --git a/kokoro/gcp_ubuntu_per_language/go/bazel/run_tests.sh b/kokoro/gcp_ubuntu_per_language/go/bazel/run_tests.sh
new file mode 100644
index 0000000..b845951
--- /dev/null
+++ b/kokoro/gcp_ubuntu_per_language/go/bazel/run_tests.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -euo pipefail
+
+cd "${KOKORO_ARTIFACTS_DIR}/git/tink/go"
+use_bazel.sh "$(cat .bazelversion)"
+time bazel build -- ...
+time bazel test -- ...
diff --git a/kokoro/gcp_ubuntu_per_language/go/common.cfg b/kokoro/gcp_ubuntu_per_language/go/common.cfg
deleted file mode 100644
index 63fb24c..0000000
--- a/kokoro/gcp_ubuntu_per_language/go/common.cfg
+++ /dev/null
@@ -1,3 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-build_file: "tink/kokoro/gcp_ubuntu_per_language/go/run_tests.sh"
diff --git a/kokoro/gcp_ubuntu_per_language/go/gomod/common.cfg b/kokoro/gcp_ubuntu_per_language/go/gomod/common.cfg
new file mode 100644
index 0000000..7d31099
--- /dev/null
+++ b/kokoro/gcp_ubuntu_per_language/go/gomod/common.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "tink/kokoro/gcp_ubuntu_per_language/go/gomod/run_tests.sh"
diff --git a/kokoro/gcp_ubuntu_per_language/go/continuous.cfg b/kokoro/gcp_ubuntu_per_language/go/gomod/continuous.cfg
similarity index 100%
copy from kokoro/gcp_ubuntu_per_language/go/continuous.cfg
copy to kokoro/gcp_ubuntu_per_language/go/gomod/continuous.cfg
diff --git a/kokoro/gcp_ubuntu_per_language/go/presubmit.cfg b/kokoro/gcp_ubuntu_per_language/go/gomod/presubmit.cfg
similarity index 100%
copy from kokoro/gcp_ubuntu_per_language/go/presubmit.cfg
copy to kokoro/gcp_ubuntu_per_language/go/gomod/presubmit.cfg
diff --git a/kokoro/gcp_ubuntu_per_language/go/gomod/run_tests.sh b/kokoro/gcp_ubuntu_per_language/go/gomod/run_tests.sh
new file mode 100644
index 0000000..fe4c12d
--- /dev/null
+++ b/kokoro/gcp_ubuntu_per_language/go/gomod/run_tests.sh
@@ -0,0 +1,126 @@
+#!/bin/bash
+
+set -euo pipefail
+
+REPO_DIR="${KOKORO_ARTIFACTS_DIR}/git/tink"
+
+TINK_VERSION="$(cat ${REPO_DIR}/tink_version.bzl | grep ^TINK | cut -f 2 -d \")"
+
+# Create a temporary directory for performing module tests.
+TMP_DIR="$(mktemp -dt go-module-test.XXXXXX)"
+GO_MOD_DIR="${TMP_DIR}/go-mod-test"
+
+REPO_URL_PREFIX="github.com/google/tink"
+
+#######################################
+# Test an individual Go module within the Tink repository.
+# Globals:
+#   REPO_DIR
+#   TINK_VERISON
+#   GO_MOD_DIR
+#   REPO_URL_PREFIX
+# Arguments:
+#   The name of the Go module, relative to the repository root.
+# Outputs:
+#   Prints progress to STDOUT.
+#######################################
+function test_go_mod() {
+  local mod_name="$1"
+  local full_mod_name="${REPO_URL_PREFIX}/${mod_name}"
+
+  echo "### Testing ${full_mod_name}..."
+  mkdir "${GO_MOD_DIR}"
+  (
+    cd "${GO_MOD_DIR}"
+
+    # Display commands being run for the remainder of this subshell.
+    set -x
+
+    # Initialize a test Go module.
+    go mod init tink-go-mod-test
+    overlay_module "${mod_name}" "${full_mod_name}"
+    overlay_internal_deps "${mod_name}"
+
+    # Print the prepared go.mod.
+    cat go.mod
+
+    # Get the module at the latest commit and print graph output depicting
+    # direct dependencies.
+    go get -v "${full_mod_name}@master"
+
+    # Pint contextual information concerning dependencies.
+    go mod graph | grep google/tink
+    go list -m all | grep google/tink
+  )
+
+  # Leave a clean environment for subsequent tests.
+  go clean -modcache
+  rm -rf "${GO_MOD_DIR}"
+}
+
+#######################################
+# Add a require statement for a Tink module and a replace statement to point it
+# to the local copy.
+# Globals:
+#   REPO_DIR
+#   TINK_VERISON
+# Arguments:
+#   The name of the Go module, relative to the repository root.
+#   The full name of the Go module, as specified in import statements.
+#######################################
+function overlay_module() {
+  local mod_name="$1"
+  local full_mod_name="$2"
+
+  go mod edit "-require=${full_mod_name}@v${TINK_VERSION}"
+  go mod edit "-replace=${full_mod_name}=${REPO_DIR}/${mod_name}"
+}
+
+#######################################
+# Search the go.mod being tested for internal dependencies and overlay them with
+# the local copies.
+# Globals:
+#   REPO_DIR
+#   REPO_URL_PREFIX
+# Arguments:
+#   The name of the Go module being tested, relative to the repository root.
+#######################################
+function overlay_internal_deps() {
+  local mod_name="$1"
+
+  declare -a internal_deps
+  while read internal_dep; do
+    internal_deps+=("${internal_dep}")
+  done < <(grep "${REPO_URL_PREFIX}" "${REPO_DIR}/${mod_name}/go.mod" \
+      | grep -v ^module \
+      | awk '{print $1}')
+
+  # If internal_deps are found...
+  if [[ ! -z "${internal_deps+x}" ]]; then
+    for full_dep_name in "${internal_deps[@]}"; do
+      local dep_name="$(echo "${full_dep_name}" | sed "s#${REPO_URL_PREFIX}/##")"
+      overlay_module "${dep_name}" "${full_dep_name}"
+    done
+  fi
+}
+
+function main() {
+  # Extract all go.mod instances from the repository.
+  declare -a go_mod_dirs
+  while read go_mod_dir; do
+    go_mod_dirs+=("${go_mod_dir}")
+  done < <(find "${REPO_DIR}" -name "go.mod" \
+    | sed "s#^${REPO_DIR}/##" \
+    | xargs -n 1 dirname)
+
+  echo "### Go modules found:"
+  for go_mod_dir in "${go_mod_dirs[@]}"; do
+    echo "${go_mod_dir}"
+  done
+
+  for go_mod_dir in "${go_mod_dirs[@]}"; do
+    test_go_mod "${go_mod_dir}"
+  done
+}
+
+main "$@"
diff --git a/kokoro/gcp_ubuntu_per_language/go/run_tests.sh b/kokoro/gcp_ubuntu_per_language/go/run_tests.sh
deleted file mode 100644
index a7d127d..0000000
--- a/kokoro/gcp_ubuntu_per_language/go/run_tests.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-
-set -euo pipefail
-cd ${KOKORO_ARTIFACTS_DIR}/git/tink
-
-cd go
-use_bazel.sh $(cat .bazelversion)
-time bazel build -- ...
-time bazel test -- ...
diff --git a/kokoro/gcp_ubuntu_per_language/python/pip/run_tests.sh b/kokoro/gcp_ubuntu_per_language/python/pip/run_tests.sh
index 606a7c4..9bf6571 100644
--- a/kokoro/gcp_ubuntu_per_language/python/pip/run_tests.sh
+++ b/kokoro/gcp_ubuntu_per_language/python/pip/run_tests.sh
@@ -1,8 +1,7 @@
 #!/bin/bash
 
 set -euo pipefail
-cd ${KOKORO_ARTIFACTS_DIR}/git/tink
-
+cd ${KOKORO_ARTIFACTS_DIR}/git/tink/python
 install_python3() {
     : "${PYTHON_VERSION:=3.7.1}"
 
@@ -17,11 +16,8 @@
     pyenv global "${PYTHON_VERSION}"
 }
 
-
 install_pip_package() {
   # Check if we can build Tink python package.
-  (
-    cd python
     # Needed for setuptools
 
     use_bazel.sh $(cat .bazelversion)
@@ -29,12 +25,28 @@
     PROTOC_ZIP='protoc-3.11.4-linux-x86_64.zip'
     curl -OL "https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/${PROTOC_ZIP}"
     sudo unzip -o "${PROTOC_ZIP}" -d /usr/local bin/protoc
+
+    # Set path to Tink base folder
+    export TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH=$PWD/..
+
     # Update pip and start setup
     pip3 install --upgrade pip
     pip3 install --upgrade setuptools
     pip3 install .
-  )
 }
 
+run_tests_with_package() {
+  # Set path to Tink base folder
+  export TINK_SRC_PATH=${PWD}/..
+
+  # Run Python tests directly so the package is used.
+  # We exclude tests in tink/cc/pybind: they are implementation details and may
+  # depend on a testonly shared object.
+  find tink/ -not -path "*cc/pybind*" -type f -name "*_test.py" -print0 | xargs -0 -n1 python3
+}
 install_python3
 install_pip_package
+run_tests_with_package
+
+# Generate release of the pip package and test it
+./tools/distribution/create_release.sh
diff --git a/kokoro/macos_external/go/bazel/common.cfg b/kokoro/macos_external/go/bazel/common.cfg
new file mode 100644
index 0000000..58de5cd
--- /dev/null
+++ b/kokoro/macos_external/go/bazel/common.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "tink/kokoro/macos_external/go/bazel/run_tests.sh"
diff --git a/kokoro/macos_external/go/continuous.cfg b/kokoro/macos_external/go/bazel/continuous.cfg
similarity index 100%
rename from kokoro/macos_external/go/continuous.cfg
rename to kokoro/macos_external/go/bazel/continuous.cfg
diff --git a/kokoro/macos_external/go/presubmit.cfg b/kokoro/macos_external/go/bazel/presubmit.cfg
similarity index 100%
rename from kokoro/macos_external/go/presubmit.cfg
rename to kokoro/macos_external/go/bazel/presubmit.cfg
diff --git a/kokoro/macos_external/go/bazel/run_tests.sh b/kokoro/macos_external/go/bazel/run_tests.sh
new file mode 100644
index 0000000..b845951
--- /dev/null
+++ b/kokoro/macos_external/go/bazel/run_tests.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+set -euo pipefail
+
+cd "${KOKORO_ARTIFACTS_DIR}/git/tink/go"
+use_bazel.sh "$(cat .bazelversion)"
+time bazel build -- ...
+time bazel test -- ...
diff --git a/kokoro/macos_external/go/common.cfg b/kokoro/macos_external/go/common.cfg
deleted file mode 100644
index 5d05f0b..0000000
--- a/kokoro/macos_external/go/common.cfg
+++ /dev/null
@@ -1,3 +0,0 @@
-# Format: //devtools/kokoro/config/proto/build.proto
-
-build_file: "tink/kokoro/macos_external/go/run_tests.sh"
diff --git a/kokoro/macos_external/go/gomod/common.cfg b/kokoro/macos_external/go/gomod/common.cfg
new file mode 100644
index 0000000..ee085b3
--- /dev/null
+++ b/kokoro/macos_external/go/gomod/common.cfg
@@ -0,0 +1,3 @@
+# Format: //devtools/kokoro/config/proto/build.proto
+
+build_file: "tink/kokoro/macos_external/go/gomod/run_tests.sh"
diff --git a/kokoro/macos_external/go/continuous.cfg b/kokoro/macos_external/go/gomod/continuous.cfg
similarity index 100%
copy from kokoro/macos_external/go/continuous.cfg
copy to kokoro/macos_external/go/gomod/continuous.cfg
diff --git a/kokoro/macos_external/go/presubmit.cfg b/kokoro/macos_external/go/gomod/presubmit.cfg
similarity index 100%
copy from kokoro/macos_external/go/presubmit.cfg
copy to kokoro/macos_external/go/gomod/presubmit.cfg
diff --git a/kokoro/macos_external/go/gomod/run_tests.sh b/kokoro/macos_external/go/gomod/run_tests.sh
new file mode 100644
index 0000000..fe4c12d
--- /dev/null
+++ b/kokoro/macos_external/go/gomod/run_tests.sh
@@ -0,0 +1,126 @@
+#!/bin/bash
+
+set -euo pipefail
+
+REPO_DIR="${KOKORO_ARTIFACTS_DIR}/git/tink"
+
+TINK_VERSION="$(cat ${REPO_DIR}/tink_version.bzl | grep ^TINK | cut -f 2 -d \")"
+
+# Create a temporary directory for performing module tests.
+TMP_DIR="$(mktemp -dt go-module-test.XXXXXX)"
+GO_MOD_DIR="${TMP_DIR}/go-mod-test"
+
+REPO_URL_PREFIX="github.com/google/tink"
+
+#######################################
+# Test an individual Go module within the Tink repository.
+# Globals:
+#   REPO_DIR
+#   TINK_VERISON
+#   GO_MOD_DIR
+#   REPO_URL_PREFIX
+# Arguments:
+#   The name of the Go module, relative to the repository root.
+# Outputs:
+#   Prints progress to STDOUT.
+#######################################
+function test_go_mod() {
+  local mod_name="$1"
+  local full_mod_name="${REPO_URL_PREFIX}/${mod_name}"
+
+  echo "### Testing ${full_mod_name}..."
+  mkdir "${GO_MOD_DIR}"
+  (
+    cd "${GO_MOD_DIR}"
+
+    # Display commands being run for the remainder of this subshell.
+    set -x
+
+    # Initialize a test Go module.
+    go mod init tink-go-mod-test
+    overlay_module "${mod_name}" "${full_mod_name}"
+    overlay_internal_deps "${mod_name}"
+
+    # Print the prepared go.mod.
+    cat go.mod
+
+    # Get the module at the latest commit and print graph output depicting
+    # direct dependencies.
+    go get -v "${full_mod_name}@master"
+
+    # Pint contextual information concerning dependencies.
+    go mod graph | grep google/tink
+    go list -m all | grep google/tink
+  )
+
+  # Leave a clean environment for subsequent tests.
+  go clean -modcache
+  rm -rf "${GO_MOD_DIR}"
+}
+
+#######################################
+# Add a require statement for a Tink module and a replace statement to point it
+# to the local copy.
+# Globals:
+#   REPO_DIR
+#   TINK_VERISON
+# Arguments:
+#   The name of the Go module, relative to the repository root.
+#   The full name of the Go module, as specified in import statements.
+#######################################
+function overlay_module() {
+  local mod_name="$1"
+  local full_mod_name="$2"
+
+  go mod edit "-require=${full_mod_name}@v${TINK_VERSION}"
+  go mod edit "-replace=${full_mod_name}=${REPO_DIR}/${mod_name}"
+}
+
+#######################################
+# Search the go.mod being tested for internal dependencies and overlay them with
+# the local copies.
+# Globals:
+#   REPO_DIR
+#   REPO_URL_PREFIX
+# Arguments:
+#   The name of the Go module being tested, relative to the repository root.
+#######################################
+function overlay_internal_deps() {
+  local mod_name="$1"
+
+  declare -a internal_deps
+  while read internal_dep; do
+    internal_deps+=("${internal_dep}")
+  done < <(grep "${REPO_URL_PREFIX}" "${REPO_DIR}/${mod_name}/go.mod" \
+      | grep -v ^module \
+      | awk '{print $1}')
+
+  # If internal_deps are found...
+  if [[ ! -z "${internal_deps+x}" ]]; then
+    for full_dep_name in "${internal_deps[@]}"; do
+      local dep_name="$(echo "${full_dep_name}" | sed "s#${REPO_URL_PREFIX}/##")"
+      overlay_module "${dep_name}" "${full_dep_name}"
+    done
+  fi
+}
+
+function main() {
+  # Extract all go.mod instances from the repository.
+  declare -a go_mod_dirs
+  while read go_mod_dir; do
+    go_mod_dirs+=("${go_mod_dir}")
+  done < <(find "${REPO_DIR}" -name "go.mod" \
+    | sed "s#^${REPO_DIR}/##" \
+    | xargs -n 1 dirname)
+
+  echo "### Go modules found:"
+  for go_mod_dir in "${go_mod_dirs[@]}"; do
+    echo "${go_mod_dir}"
+  done
+
+  for go_mod_dir in "${go_mod_dirs[@]}"; do
+    test_go_mod "${go_mod_dir}"
+  done
+}
+
+main "$@"
diff --git a/kokoro/macos_external/go/run_tests.sh b/kokoro/macos_external/go/run_tests.sh
deleted file mode 100644
index a7d127d..0000000
--- a/kokoro/macos_external/go/run_tests.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-
-set -euo pipefail
-cd ${KOKORO_ARTIFACTS_DIR}/git/tink
-
-cd go
-use_bazel.sh $(cat .bazelversion)
-time bazel build -- ...
-time bazel test -- ...
diff --git a/kokoro/macos_external/python/pip/run_tests.sh b/kokoro/macos_external/python/pip/run_tests.sh
index c74ce8b..b0322e7 100644
--- a/kokoro/macos_external/python/pip/run_tests.sh
+++ b/kokoro/macos_external/python/pip/run_tests.sh
@@ -15,6 +15,10 @@
     PROTOC_ZIP=protoc-3.11.4-osx-x86_64.zip
     curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/$PROTOC_ZIP
     sudo unzip -o $PROTOC_ZIP -d /usr/local bin/protoc
+
+    # Set path to Tink base folder
+    export TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH=$PWD/..
+
     # Update pip and install all requirements. Note that on MacOS we need to
     # use the --user flag as otherwise pip will complain about permissions.
     pip3 install --upgrade pip --user
@@ -23,4 +27,19 @@
   )
 }
 
+run_tests_with_package() {
+  # Get root certificates for gRPC
+  wget https://raw.githubusercontent.com/grpc/grpc/master/etc/roots.pem
+  export GRPC_DEFAULT_SSL_ROOTS_FILE_PATH=${PWD}/roots.pem
+
+  # Set path to Tink base folder
+  export TINK_SRC_PATH=${PWD}
+
+  # Run Python tests directly so the package is used.
+  # We exclude tests in tink/cc/pybind: they are implementation details and may
+  # depend on a testonly shared object.
+  find python/tink/ -not -path "*cc/pybind*" -type f -name "*_test.py" -print0 | xargs -0 -n1 python3
+}
+
 install_pip_package
+run_tests_with_package
diff --git a/kokoro/presubmit.sh b/kokoro/presubmit.sh
index 6f6135b..7a017c1 100755
--- a/kokoro/presubmit.sh
+++ b/kokoro/presubmit.sh
@@ -24,7 +24,4 @@
 # Change to repo root
 cd git*/tink
 
-# Test that Tink can be installed with the standard Go tooling.
-go get github.com/google/tink/go/...
-
 ./kokoro/run_tests.sh
diff --git a/maven/README.md b/maven/README.md
index 9085fdf..534b66a 100644
--- a/maven/README.md
+++ b/maven/README.md
@@ -2,14 +2,29 @@
 
 Tink Java has 4 Maven artifacts, all are in the `com.google.crypto.tink` group.
 
--   `tink`: this is the core of Tink built for server side apps. It only depends
-    on `org.json:json` and `com.google.protobuf:protobuf-java`.
--   `tink-android`: this is similar to `tink` but is built for Android apps. It
-    only depends on `com.google.protobuf:protobuf-lite`.
--   `tink-awskms`: this is a plugin for the `tink` artifact that integrates Tink
-    with AWS KMS.
--   `tink-gcpkms`: this is a plugin for the `tink` artifact that integrates Tink
-    with GCP KMS.
+### `tink`
+
+The core of Tink built for server-side applications. It only depends on
+[`org.json:json`](https://search.maven.org/artifact/org.json/json) and
+[`com.google.protobuf:protobuf-java`](https://search.maven.org/artifact/com.google.protobuf/protobuf-java).
+
+### `tink-android`
+
+Similar to `tink` but is built for Android applicationss.
+
+This build includes an embeded copy
+[`com.google.protobuf:protobuf-lite`](https://search.maven.org/artifact/com.google.protobuf/protobuf-javalite),
+which is renamed to be `com.google.crypto.tink.shaded.protobuf`. This is done to
+avoid dependency conflicts with other common dependencies that depend on a
+conflicting version (e.g.  Firebase).
+
+### `tink-awskms`
+
+A plugin for the `tink` artifact that integrates Tink with AWS KMS.
+
+### `tink-gcpkms`
+
+A plugin for the `tink` artifact that integrates Tink with GCP KMS.
 
 ## Publishing snapshots
 
@@ -20,8 +35,8 @@
 This command publishes latest snapshots to Maven, and their Javadocs to
 https://google.github.com/tink.
 
-Snapshots are also automatically published for every new commit to
-the master branch of https://github.com/google/tink.
+Snapshots are automatically published for every new commit to the master branch
+of https://github.com/google/tink.
 
 ## Testing snapshots
 
diff --git a/maven/execute-deploy.sh b/maven/execute-deploy.sh
index 93f4c32..1d309cc 100755
--- a/maven/execute-deploy.sh
+++ b/maven/execute-deploy.sh
@@ -218,7 +218,7 @@
   deploy_library \
     tink-android \
     java_src \
-    tink-android-shaded.jar \
+    tink-android.jar \
     tink-android-src.jar \
     tink-android-javadoc.jar \
     "../$(dirname $0)/tink-android.pom.xml"
diff --git a/objc/Tests/UnitTests/aead/TINKAeadKeyTemplateTest.mm b/objc/Tests/UnitTests/aead/TINKAeadKeyTemplateTest.mm
index c764491..65c1251 100644
--- a/objc/Tests/UnitTests/aead/TINKAeadKeyTemplateTest.mm
+++ b/objc/Tests/UnitTests/aead/TINKAeadKeyTemplateTest.mm
@@ -24,13 +24,6 @@
 
 #import "objc/TINKKeyTemplate.h"
 #import "objc/core/TINKKeyTemplate_Internal.h"
-#import "objc/util/TINKProtoHelpers.h"
-#import "proto/AesCtr.pbobjc.h"
-#import "proto/AesCtrHmacAead.pbobjc.h"
-#import "proto/AesGcm.pbobjc.h"
-#import "proto/Common.pbobjc.h"
-#import "proto/Hmac.pbobjc.h"
-#import "proto/Tink.pbobjc.h"
 
 using google::crypto::tink::XChaCha20Poly1305KeyFormat;
 
@@ -40,7 +33,7 @@
 @implementation TINKAeadKeyTemplatesTest
 
 - (void)testAesGcmKeyTemplates {
-  static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.AesGcmKey";
+  static std::string const kTypeURL = "type.googleapis.com/google.crypto.tink.AesGcmKey";
 
   NSError *error = nil;
   // AES-128 GCM
@@ -49,41 +42,22 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBAesGcmKeyFormat *keyFormat =
-      [TINKPBAesGcmKeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-  XCTAssertTrue(16 == keyFormat.keySize);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 
   // AES-256 GCM
   tpl = [[TINKAeadKeyTemplate alloc] initWithKeyTemplate:TINKAes256Gcm error:&error];
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-  error = nil;
-  keyFormat = [TINKPBAesGcmKeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-  XCTAssertTrue(32 == keyFormat.keySize);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testAesCtrHmacKeyTemplates {
-  NSString *kTypeURL = @"type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
+  static std::string const kTypeURL = "type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey";
 
   // AES-128 CTR HMAC SHA-256
   NSError *error = nil;
@@ -92,49 +66,22 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBAesCtrHmacAeadKeyFormat *keyFormat =
-      [TINKPBAesCtrHmacAeadKeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-  XCTAssertTrue(16 == keyFormat.aesCtrKeyFormat.keySize);
-  XCTAssertTrue(16 == keyFormat.aesCtrKeyFormat.params.ivSize);
-  XCTAssertTrue(32 == keyFormat.hmacKeyFormat.keySize);
-  XCTAssertTrue(16 == keyFormat.hmacKeyFormat.params.tagSize);
-  XCTAssertTrue(TINKPBHashType_Sha256 == keyFormat.hmacKeyFormat.params.hash_p);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 
   // AES-256 CTR HMAC SHA-256
   tpl = [[TINKAeadKeyTemplate alloc] initWithKeyTemplate:TINKAes256CtrHmacSha256 error:&error];
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-  error = nil;
-  keyFormat = [TINKPBAesCtrHmacAeadKeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-  XCTAssertTrue(32 == keyFormat.aesCtrKeyFormat.keySize);
-  XCTAssertTrue(16 == keyFormat.aesCtrKeyFormat.params.ivSize);
-  XCTAssertTrue(32 == keyFormat.hmacKeyFormat.keySize);
-  XCTAssertTrue(32 == keyFormat.hmacKeyFormat.params.tagSize);
-  XCTAssertTrue(TINKPBHashType_Sha256 == keyFormat.hmacKeyFormat.params.hash_p);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testAesEaxKeyTemplates {
-  static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.AesEaxKey";
+  static std::string const kTypeURL = "type.googleapis.com/google.crypto.tink.AesEaxKey";
 
   NSError *error = nil;
   // AES-128 EAX
@@ -143,41 +90,22 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBAesGcmKeyFormat *keyFormat =
-      [TINKPBAesGcmKeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-  XCTAssertEqual(keyFormat.keySize, 16);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 
   // AES-256 EAX
   tpl = [[TINKAeadKeyTemplate alloc] initWithKeyTemplate:TINKAes256Eax error:&error];
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-  error = nil;
-  keyFormat = [TINKPBAesGcmKeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-  XCTAssertEqual(keyFormat.keySize, 32);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testXChaCha20Poly1305KeyTemplates {
-  static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
+  static std::string const kTypeURL = "type.googleapis.com/google.crypto.tink.XChaCha20Poly1305Key";
 
   NSError *error = nil;
   TINKAeadKeyTemplate *tpl = [[TINKAeadKeyTemplate alloc] initWithKeyTemplate:TINKXChaCha20Poly1305
@@ -185,22 +113,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-  error = nil;
-
-  // Check that the template works with the key manager.
-  crypto::tink::XChaCha20Poly1305KeyManager key_manager;
-  XCTAssertTrue(key_manager.get_key_type() == tpl.ccKeyTemplate->type_url());
-  XChaCha20Poly1305KeyFormat key_format;
-  XCTAssertTrue(key_format.ParseFromString(tpl.ccKeyTemplate->value()));
-  auto validation = key_manager.ValidateKeyFormat(key_format);
-  XCTAssertTrue(validation.ok());
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 @end
diff --git a/objc/Tests/UnitTests/core/TINKBinaryKeysetReaderTest.mm b/objc/Tests/UnitTests/core/TINKBinaryKeysetReaderTest.mm
index 04c613b..13d9534 100644
--- a/objc/Tests/UnitTests/core/TINKBinaryKeysetReaderTest.mm
+++ b/objc/Tests/UnitTests/core/TINKBinaryKeysetReaderTest.mm
@@ -21,7 +21,6 @@
 #import <XCTest/XCTest.h>
 
 #import "objc/util/TINKStrings.h"
-#import "proto/Tink.pbobjc.h"
 
 #include "tink/util/test_util.h"
 #include "proto/tink.pb.h"
diff --git a/objc/Tests/UnitTests/core/TINKJSONKeysetReaderTest.mm b/objc/Tests/UnitTests/core/TINKJSONKeysetReaderTest.mm
index bed6128..1cab383 100644
--- a/objc/Tests/UnitTests/core/TINKJSONKeysetReaderTest.mm
+++ b/objc/Tests/UnitTests/core/TINKJSONKeysetReaderTest.mm
@@ -21,7 +21,6 @@
 #import <XCTest/XCTest.h>
 
 #import "objc/util/TINKStrings.h"
-#import "proto/Tink.pbobjc.h"
 
 #include "google/protobuf/util/json_util.h"
 #include "tink/util/test_util.h"
diff --git a/objc/Tests/UnitTests/core/TINKKeysetHandleTest.mm b/objc/Tests/UnitTests/core/TINKKeysetHandleTest.mm
index 32c2abe..9809359 100644
--- a/objc/Tests/UnitTests/core/TINKKeysetHandleTest.mm
+++ b/objc/Tests/UnitTests/core/TINKKeysetHandleTest.mm
@@ -31,7 +31,6 @@
 #import "objc/TINKSignatureKeyTemplate.h"
 #import "objc/aead/TINKAeadInternal.h"
 #import "objc/util/TINKStrings.h"
-#import "proto/Tink.pbobjc.h"
 
 #include "tink/binary_keyset_reader.h"
 #include "tink/util/status.h"
@@ -41,6 +40,7 @@
 
 using ::crypto::tink::test::AddRawKey;
 using ::crypto::tink::test::AddTinkKey;
+using ::google::crypto::tink::EncryptedKeyset;
 using ::google::crypto::tink::KeyData;
 using ::google::crypto::tink::Keyset;
 using ::google::crypto::tink::KeyStatusType;
@@ -57,7 +57,7 @@
 static NSString *const kBadKeysetName = @"com.google.crypto.tink.badKeyset";
 static NSString *const kNonExistentKeysetName = @"com.google.crypto.tink.noSuchKeyset";
 
-static TINKPBKeyset *gKeyset;
+static Keyset *gKeyset;
 
 @interface TINKKeysetHandleTest : XCTestCase
 @end
@@ -65,25 +65,22 @@
 @implementation TINKKeysetHandleTest
 
 + (void)setUp {
-  google::crypto::tink::Keyset ccKeyset;
+  gKeyset = new Keyset();
   google::crypto::tink::Keyset::Key ccKey;
 
   crypto::tink::test::AddTinkKey("some key type", 42, ccKey,
                                  google::crypto::tink::KeyStatusType::ENABLED,
-                                 google::crypto::tink::KeyData::SYMMETRIC, &ccKeyset);
+                                 google::crypto::tink::KeyData::SYMMETRIC, gKeyset);
   crypto::tink::test::AddRawKey("some other key type", 711, ccKey,
                                 google::crypto::tink::KeyStatusType::ENABLED,
-                                google::crypto::tink::KeyData::SYMMETRIC, &ccKeyset);
-  ccKeyset.set_primary_key_id(42);
+                                google::crypto::tink::KeyData::SYMMETRIC, gKeyset);
+  gKeyset->set_primary_key_id(42);
 
-  std::string serializedKeyset = ccKeyset.SerializeAsString();
+  std::string serializedKeyset = gKeyset->SerializeAsString();
   gGoodSerializedKeyset = TINKStringToNSData(serializedKeyset);
 
   NSError *error = nil;
-  gKeyset = [TINKPBKeyset
-      parseFromData:[NSData dataWithBytes:serializedKeyset.data() length:serializedKeyset.length()]
-              error:&error];
-  XCTAssertNotNil(gKeyset);
+  XCTAssertTrue(gKeyset != nil);
   XCTAssertNil(error);
 
   gBadSerializedKeyset = TINKStringToNSData("some weird string");
@@ -130,15 +127,21 @@
       std::unique_ptr<crypto::tink::Aead>(new crypto::tink::test::DummyAead("dummy aead 42"));
   TINKAeadInternal *aead = [[TINKAeadInternal alloc] initWithCCAead:std::move(ccAead)];
 
-  NSData *keysetCiphertext = [aead encrypt:gKeyset.data withAdditionalData:[NSData data] error:nil];
+  std::string serializedKeyset = gKeyset->SerializeAsString();
+  NSData *serializedKeysetData = [[NSData alloc] initWithBytes:serializedKeyset.data()
+                                                        length:serializedKeyset.size()];
+  NSData *keysetCiphertext = [aead encrypt:serializedKeysetData
+                        withAdditionalData:[NSData data]
+                                     error:nil];
 
   XCTAssertNotNil(keysetCiphertext);
 
-  TINKPBEncryptedKeyset *encryptedKeyset = [[TINKPBEncryptedKeyset alloc] init];
-  encryptedKeyset.encryptedKeyset = keysetCiphertext;
+  EncryptedKeyset encryptedKeyset;
+  encryptedKeyset.set_encrypted_keyset(NSDataToTINKString(keysetCiphertext));
 
-  TINKBinaryKeysetReader *reader =
-      [[TINKBinaryKeysetReader alloc] initWithSerializedKeyset:encryptedKeyset.data error:nil];
+  TINKBinaryKeysetReader *reader = [[TINKBinaryKeysetReader alloc]
+      initWithSerializedKeyset:TINKStringToNSData(encryptedKeyset.SerializeAsString())
+                         error:nil];
 
   TINKKeysetHandle *handle =
       [[TINKKeysetHandle alloc] initWithKeysetReader:reader andKey:aead error:nil];
@@ -146,8 +149,8 @@
   std::string output;
   crypto::tink::TestKeysetHandle::GetKeyset(*handle.ccKeysetHandle).SerializeToString(&output);
 
-  XCTAssertTrue(
-      [gKeyset.data isEqualToData:[NSData dataWithBytes:output.data() length:output.size()]]);
+  XCTAssertTrue([serializedKeysetData isEqualToData:[NSData dataWithBytes:output.data()
+                                                                   length:output.size()]]);
 }
 
 - (void)testWrongAead_Binary {
@@ -155,13 +158,20 @@
       std::unique_ptr<crypto::tink::Aead>(new crypto::tink::test::DummyAead("dummy aead 42"));
   TINKAeadInternal *aead = [[TINKAeadInternal alloc] initWithCCAead:std::move(ccAead)];
 
-  NSData *keysetCiphertext = [aead encrypt:gKeyset.data withAdditionalData:[NSData data] error:nil];
+  std::string serializedKeyset = gKeyset->SerializeAsString();
+  NSData *serializedKeysetData = [[NSData alloc] initWithBytes:serializedKeyset.data()
+                                                        length:serializedKeyset.size()];
 
-  TINKPBEncryptedKeyset *encryptedKeyset = [[TINKPBEncryptedKeyset alloc] init];
-  encryptedKeyset.encryptedKeyset = keysetCiphertext;
+  NSData *keysetCiphertext = [aead encrypt:serializedKeysetData
+                        withAdditionalData:[NSData data]
+                                     error:nil];
 
-  TINKBinaryKeysetReader *reader =
-      [[TINKBinaryKeysetReader alloc] initWithSerializedKeyset:encryptedKeyset.data error:nil];
+  EncryptedKeyset encryptedKeyset;
+  encryptedKeyset.set_encrypted_keyset(NSDataToTINKString(keysetCiphertext));
+
+  TINKBinaryKeysetReader *reader = [[TINKBinaryKeysetReader alloc]
+      initWithSerializedKeyset:TINKStringToNSData(encryptedKeyset.SerializeAsString())
+                         error:nil];
 
   auto ccWrongAead =
       std::unique_ptr<crypto::tink::Aead>(new crypto::tink::test::DummyAead("wrong aead"));
@@ -197,13 +207,14 @@
   auto ccAead =
       std::unique_ptr<crypto::tink::Aead>(new crypto::tink::test::DummyAead("dummy aead 42"));
   TINKAeadInternal *aead = [[TINKAeadInternal alloc] initWithCCAead:std::move(ccAead)];
-  NSString *keysetCiphertext = @"totally wrong ciphertext";
+  NSData *keysetCiphertext = [@"totally wrong ciphertext" dataUsingEncoding:NSUTF8StringEncoding];
 
-  TINKPBEncryptedKeyset *encryptedKeyset = [[TINKPBEncryptedKeyset alloc] init];
-  encryptedKeyset.encryptedKeyset = [keysetCiphertext dataUsingEncoding:NSUTF8StringEncoding];
+  EncryptedKeyset encryptedKeyset;
+  encryptedKeyset.set_encrypted_keyset(NSDataToTINKString(keysetCiphertext));
 
-  TINKBinaryKeysetReader *reader =
-      [[TINKBinaryKeysetReader alloc] initWithSerializedKeyset:encryptedKeyset.data error:nil];
+  TINKBinaryKeysetReader *reader = [[TINKBinaryKeysetReader alloc]
+      initWithSerializedKeyset:TINKStringToNSData(encryptedKeyset.SerializeAsString())
+                         error:nil];
   NSError *error = nil;
   TINKKeysetHandle *handle =
       [[TINKKeysetHandle alloc] initWithKeysetReader:reader andKey:aead error:&error];
@@ -231,15 +242,21 @@
       std::unique_ptr<crypto::tink::Aead>(new crypto::tink::test::DummyAead("dummy aead 42"));
   TINKAeadInternal *aead = [[TINKAeadInternal alloc] initWithCCAead:std::move(ccAead)];
 
-  NSData *keysetCiphertext = [aead encrypt:gKeyset.data withAdditionalData:[NSData data] error:nil];
+  std::string serializedKeyset = gKeyset->SerializeAsString();
+  NSData *serializedKeysetData = [[NSData alloc] initWithBytes:serializedKeyset.data()
+                                                        length:serializedKeyset.size()];
+  NSData *keysetCiphertext = [aead encrypt:serializedKeysetData
+                        withAdditionalData:[NSData data]
+                                     error:nil];
 
   XCTAssertNotNil(keysetCiphertext);
 
-  TINKPBEncryptedKeyset *encryptedKeyset = [[TINKPBEncryptedKeyset alloc] init];
-  encryptedKeyset.encryptedKeyset = keysetCiphertext;
+  EncryptedKeyset encryptedKeyset;
+  encryptedKeyset.set_encrypted_keyset(NSDataToTINKString(keysetCiphertext));
 
-  TINKBinaryKeysetReader *reader =
-      [[TINKBinaryKeysetReader alloc] initWithSerializedKeyset:encryptedKeyset.data error:nil];
+  TINKBinaryKeysetReader *reader = [[TINKBinaryKeysetReader alloc]
+      initWithSerializedKeyset:TINKStringToNSData(encryptedKeyset.SerializeAsString())
+                         error:nil];
 
   TINKKeysetHandle *handle =
       [[TINKKeysetHandle alloc] initWithKeysetReader:reader andKey:aead error:nil];
diff --git a/objc/Tests/UnitTests/daead/TINKDeterministicAeadKeyTemplateTest.mm b/objc/Tests/UnitTests/daead/TINKDeterministicAeadKeyTemplateTest.mm
index 07592b4..098252b 100644
--- a/objc/Tests/UnitTests/daead/TINKDeterministicAeadKeyTemplateTest.mm
+++ b/objc/Tests/UnitTests/daead/TINKDeterministicAeadKeyTemplateTest.mm
@@ -22,10 +22,8 @@
 
 #import "objc/TINKKeyTemplate.h"
 #import "objc/core/TINKKeyTemplate_Internal.h"
-#import "objc/util/TINKProtoHelpers.h"
-#import "proto/AesSiv.pbobjc.h"
-#import "proto/Common.pbobjc.h"
-#import "proto/Tink.pbobjc.h"
+#include "proto/common.pb.h"
+#include "proto/tink.pb.h"
 
 @interface TINKDeterministicAeadKeyTemplatesTest : XCTestCase
 @end
@@ -33,7 +31,7 @@
 @implementation TINKDeterministicAeadKeyTemplatesTest
 
 - (void)testAesSivKeyTemplates {
-  static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.AesSivKey";
+  static std::string const kTypeURL = "type.googleapis.com/google.crypto.tink.AesSivKey";
 
   NSError *error = nil;
   // AES-256 SIV
@@ -42,19 +40,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBAesSivKeyFormat *keyFormat = [TINKPBAesSivKeyFormat parseFromData:keyTemplate.value
-                                                                    error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-  XCTAssertTrue(64 == keyFormat.keySize);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 @end
diff --git a/objc/Tests/UnitTests/hybrid/TINKHybridDecryptFactoryTest.mm b/objc/Tests/UnitTests/hybrid/TINKHybridDecryptFactoryTest.mm
index 0caa583..643d6c2 100644
--- a/objc/Tests/UnitTests/hybrid/TINKHybridDecryptFactoryTest.mm
+++ b/objc/Tests/UnitTests/hybrid/TINKHybridDecryptFactoryTest.mm
@@ -21,9 +21,9 @@
 #include "tink/crypto_format.h"
 #include "tink/util/status.h"
 #include "tink/util/test_keyset_handle.h"
-
-#import "proto/EciesAeadHkdf.pbobjc.h"
-#import "proto/Tink.pbobjc.h"
+#include "tink/util/test_util.h"
+#include "proto/ecies_aead_hkdf.pb.h"
+#include "proto/tink.pb.h"
 
 #import "objc/TINKConfig.h"
 #import "objc/TINKHybridConfig.h"
@@ -34,39 +34,38 @@
 #import "objc/TINKKeysetHandle.h"
 #import "objc/core/TINKKeysetHandle_Internal.h"
 #import "objc/util/TINKStrings.h"
-#import "objc/util/TINKTestHelpers.h"
 
-using crypto::tink::TestKeysetHandle;
+using ::crypto::tink::TestKeysetHandle;
+using ::crypto::tink::test::AddTinkKey;
+using ::crypto::tink::test::AddRawKey;
+using ::crypto::tink::test::AddLegacyKey;
+using ::google::crypto::tink::EciesAeadHkdfPrivateKey;
+using ::google::crypto::tink::EcPointFormat;
+using ::google::crypto::tink::EllipticCurveType;
+using ::google::crypto::tink::HashType;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
 
 @interface TINKHybridDecryptFactoryTest : XCTestCase
 @end
 
-static TINKPBEciesAeadHkdfPrivateKey *getNewEciesPrivateKey() {
-  return TINKGetEciesAesGcmHkdfTestKey(TINKPBEllipticCurveType_NistP256,
-                                       TINKPBEcPointFormat_Uncompressed, TINKPBHashType_Sha256, 32);
+static EciesAeadHkdfPrivateKey getNewEciesPrivateKey() {
+  return crypto::tink::test::GetEciesAesGcmHkdfTestKey(
+      EllipticCurveType::NIST_P256, EcPointFormat::UNCOMPRESSED, HashType::SHA256, 32);
 }
 
 @implementation TINKHybridDecryptFactoryTest
 
-- (void)testEncryptWith:(TINKPBKeyset *)publicKeyset andDecryptWith:(TINKPBKeyset *)privateKeyset {
-  NSError *error = nil;
-  std::string serializedKeyset = TINKPBSerializeToString(privateKeyset, &error);
-  XCTAssertNil(error);
-  google::crypto::tink::Keyset ccPrivateKeyset;
-  XCTAssertTrue(ccPrivateKeyset.ParseFromString(serializedKeyset));
+- (void)testEncryptWith:(Keyset *)publicKeyset andDecryptWith:(Keyset *)privateKeyset {
   TINKKeysetHandle *privateKeysetHandle = [[TINKKeysetHandle alloc]
-      initWithCCKeysetHandle:TestKeysetHandle::GetKeysetHandle(ccPrivateKeyset)];
+      initWithCCKeysetHandle:TestKeysetHandle::GetKeysetHandle(*privateKeyset)];
 
-  error = nil;
-  serializedKeyset = TINKPBSerializeToString(publicKeyset, &error);
-  XCTAssertNil(error);
-  google::crypto::tink::Keyset ccPublicKeyset;
-  XCTAssertTrue(ccPublicKeyset.ParseFromString(serializedKeyset));
   TINKKeysetHandle *publicKeysetHandle = [[TINKKeysetHandle alloc]
-      initWithCCKeysetHandle:TestKeysetHandle::GetKeysetHandle(ccPublicKeyset)];
+      initWithCCKeysetHandle:TestKeysetHandle::GetKeysetHandle(*publicKeyset)];
 
   // Get a HybridDecrypt primitive.
-  error = nil;
+  NSError *error = nil;
   id<TINKHybridDecrypt> hybridDecrypt =
       [TINKHybridDecryptFactory primitiveWithKeysetHandle:privateKeysetHandle error:&error];
   XCTAssertNotNil(hybridDecrypt);
@@ -126,56 +125,39 @@
   uint32_t keyId1 = 1;
   uint32_t keyId2 = 2;
   uint32_t keyId3 = 3;
-  TINKPBEciesAeadHkdfPrivateKey *eciesKey1 = getNewEciesPrivateKey();
-  TINKPBEciesAeadHkdfPrivateKey *eciesKey2 = getNewEciesPrivateKey();
-  TINKPBEciesAeadHkdfPrivateKey *eciesKey3 = getNewEciesPrivateKey();
+  EciesAeadHkdfPrivateKey eciesKey1 = getNewEciesPrivateKey();
+  EciesAeadHkdfPrivateKey eciesKey2 = getNewEciesPrivateKey();
+  EciesAeadHkdfPrivateKey eciesKey3 = getNewEciesPrivateKey();
 
-  NSString *privateKeyType = @"type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey";
-  TINKPBKeyset_Key *tinkPrivateKey =
-      TINKCreateKey(privateKeyType, keyId1, eciesKey1, TINKPBOutputPrefixType_Tink,
-                    TINKPBKeyStatusType_Enabled, TINKPBKeyData_KeyMaterialType_AsymmetricPrivate);
-  TINKPBKeyset_Key *rawPrivateKey =
-      TINKCreateKey(privateKeyType, keyId2, eciesKey2, TINKPBOutputPrefixType_Raw,
-                    TINKPBKeyStatusType_Enabled, TINKPBKeyData_KeyMaterialType_AsymmetricPrivate);
-  TINKPBKeyset_Key *legacyPrivateKey =
-      TINKCreateKey(privateKeyType, keyId3, eciesKey3, TINKPBOutputPrefixType_Legacy,
-                    TINKPBKeyStatusType_Enabled, TINKPBKeyData_KeyMaterialType_AsymmetricPrivate);
+  std::string privateKeyType = "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey";
+  Keyset privateKeyset;
+  AddTinkKey(privateKeyType, keyId1, eciesKey1, KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PRIVATE,
+             &privateKeyset);
+  AddRawKey(privateKeyType, keyId2, eciesKey2, KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PRIVATE,
+            &privateKeyset);
+  AddLegacyKey(privateKeyType, keyId3, eciesKey3, KeyStatusType::ENABLED,
+               KeyData::ASYMMETRIC_PRIVATE, &privateKeyset);
 
-  NSString *publicKeyType = @"type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey";
-  TINKPBKeyset_Key *tinkPublicKey =
-      TINKCreateKey(publicKeyType, keyId1, eciesKey1.publicKey, TINKPBOutputPrefixType_Tink,
-                    TINKPBKeyStatusType_Enabled, TINKPBKeyData_KeyMaterialType_AsymmetricPublic);
-  TINKPBKeyset_Key *rawPublicKey =
-      TINKCreateKey(publicKeyType, keyId2, eciesKey2.publicKey, TINKPBOutputPrefixType_Raw,
-                    TINKPBKeyStatusType_Enabled, TINKPBKeyData_KeyMaterialType_AsymmetricPublic);
-  TINKPBKeyset_Key *legacyPublicKey =
-      TINKCreateKey(publicKeyType, keyId3, eciesKey3.publicKey, TINKPBOutputPrefixType_Legacy,
-                    TINKPBKeyStatusType_Enabled, TINKPBKeyData_KeyMaterialType_AsymmetricPublic);
+  std::string publicKeyType = "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey";
+  Keyset publicKeyset;
+  AddTinkKey(publicKeyType, keyId1, eciesKey1.public_key(), KeyStatusType::ENABLED,
+             KeyData::ASYMMETRIC_PUBLIC, &publicKeyset);
+  AddRawKey(publicKeyType, keyId2, eciesKey2.public_key(), KeyStatusType::ENABLED,
+            KeyData::ASYMMETRIC_PUBLIC, &publicKeyset);
+  AddLegacyKey(publicKeyType, keyId3, eciesKey3.public_key(), KeyStatusType::ENABLED,
+               KeyData::ASYMMETRIC_PUBLIC, &publicKeyset);
 
-  // Encrypt with tink and decrypt with tink.
-  TINKPBKeyset *privateKeyset = TINKCreateKeyset(tinkPrivateKey, rawPrivateKey, legacyPrivateKey);
-  TINKPBKeyset *publicKeyset = TINKCreateKeyset(tinkPublicKey, rawPublicKey, legacyPublicKey);
-  [self testEncryptWith:publicKeyset andDecryptWith:privateKeyset];
+  privateKeyset.set_primary_key_id(keyId1);
+  publicKeyset.set_primary_key_id(keyId3);
+  [self testEncryptWith:&publicKeyset andDecryptWith:&privateKeyset];
 
-  // Encrypt with raw and decrypt with raw.
-  privateKeyset = TINKCreateKeyset(rawPrivateKey, tinkPrivateKey, legacyPrivateKey);
-  publicKeyset = TINKCreateKeyset(rawPublicKey, tinkPublicKey, legacyPublicKey);
-  [self testEncryptWith:publicKeyset andDecryptWith:privateKeyset];
+  privateKeyset.set_primary_key_id(keyId2);
+  publicKeyset.set_primary_key_id(keyId3);
+  [self testEncryptWith:&publicKeyset andDecryptWith:&privateKeyset];
 
-  // Encrypt with legacy and decrypt with legacy
-  privateKeyset = TINKCreateKeyset(legacyPrivateKey, tinkPrivateKey, rawPrivateKey);
-  publicKeyset = TINKCreateKeyset(legacyPublicKey, tinkPublicKey, rawPublicKey);
-  [self testEncryptWith:publicKeyset andDecryptWith:privateKeyset];
-
-  // Encrypt with tink as primary, decrypt with raw as primary.
-  publicKeyset = TINKCreateKeyset(tinkPublicKey, legacyPublicKey, rawPublicKey);
-  privateKeyset = TINKCreateKeyset(rawPrivateKey, tinkPrivateKey, legacyPrivateKey);
-  [self testEncryptWith:publicKeyset andDecryptWith:privateKeyset];
-
-  // Encrypt with raw as primary, decrypt with tink as primary.
-  publicKeyset = TINKCreateKeyset(rawPublicKey, tinkPublicKey, legacyPublicKey);
-  privateKeyset = TINKCreateKeyset(tinkPrivateKey, rawPrivateKey, legacyPrivateKey);
-  [self testEncryptWith:publicKeyset andDecryptWith:privateKeyset];
+  privateKeyset.set_primary_key_id(keyId3);
+  publicKeyset.set_primary_key_id(keyId1);
+  [self testEncryptWith:&publicKeyset andDecryptWith:&privateKeyset];
 }
 
 @end
diff --git a/objc/Tests/UnitTests/hybrid/TINKHybridEncryptFactoryTest.mm b/objc/Tests/UnitTests/hybrid/TINKHybridEncryptFactoryTest.mm
index 2c58b5d..e9a193f 100644
--- a/objc/Tests/UnitTests/hybrid/TINKHybridEncryptFactoryTest.mm
+++ b/objc/Tests/UnitTests/hybrid/TINKHybridEncryptFactoryTest.mm
@@ -20,12 +20,10 @@
 
 #include "tink/util/status.h"
 #include "tink/util/test_keyset_handle.h"
+#include "tink/util/test_util.h"
+#include "proto/ecies_aead_hkdf.pb.h"
 #include "proto/tink.pb.h"
 
-#import "proto/Common.pbobjc.h"
-#import "proto/EciesAeadHkdf.pbobjc.h"
-#import "proto/Tink.pbobjc.h"
-
 #import "objc/TINKConfig.h"
 #import "objc/TINKHybridConfig.h"
 #import "objc/TINKHybridEncrypt.h"
@@ -33,18 +31,25 @@
 #import "objc/TINKKeysetHandle.h"
 #import "objc/core/TINKKeysetHandle_Internal.h"
 #import "objc/util/TINKStrings.h"
-#import "objc/util/TINKTestHelpers.h"
 
-using crypto::tink::TestKeysetHandle;
+using ::crypto::tink::TestKeysetHandle;
+using ::crypto::tink::test::AddTinkKey;
+using ::crypto::tink::test::AddRawKey;
+using ::crypto::tink::test::AddLegacyKey;
+using ::google::crypto::tink::EciesAeadHkdfPublicKey;
+using ::google::crypto::tink::EcPointFormat;
+using ::google::crypto::tink::EllipticCurveType;
+using ::google::crypto::tink::HashType;
+using ::google::crypto::tink::KeyData;
+using ::google::crypto::tink::Keyset;
+using ::google::crypto::tink::KeyStatusType;
 
 @interface TINKHybridEncryptFactoryTest : XCTestCase
 @end
 
-static TINKPBEciesAeadHkdfPublicKey *getNewEciesPublicKey() {
-  TINKPBEciesAeadHkdfPrivateKey *eciesKey =
-      TINKGetEciesAesGcmHkdfTestKey(TINKPBEllipticCurveType_NistP256,
-                                    TINKPBEcPointFormat_Uncompressed, TINKPBHashType_Sha256, 32);
-  return eciesKey.publicKey;
+static EciesAeadHkdfPublicKey getNewEciesPublicKey() {
+  return crypto::tink::test::GetEciesAesGcmHkdfTestKey(
+      EllipticCurveType::NIST_P256, EcPointFormat::UNCOMPRESSED, HashType::SHA256, 32).public_key();
 }
 
 @implementation TINKHybridEncryptFactoryTest
@@ -68,50 +73,35 @@
 
 - (void)testPrimitiveWithKeyset {
   // Prepare a Keyset.
-  TINKPBKeyset *keyset = [[TINKPBKeyset alloc] init];
-  NSString *keyType = @"type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey";
+  Keyset publicKeyset;
+  std::string publicKeyType = "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey";
 
-  uint32_t key_id_1 = 1234543;
-  TINKAddTinkKey(keyType, key_id_1, getNewEciesPublicKey(), TINKPBKeyStatusType_Enabled,
-                 TINKPBKeyData_KeyMaterialType_AsymmetricPublic, keyset);
+  uint32_t keyId1 = 1234543;
+  uint32_t keyId2 = 726329;
+  uint32_t keyId3 = 7213743;
+  EciesAeadHkdfPublicKey eciesKey1 = getNewEciesPublicKey();
+  EciesAeadHkdfPublicKey eciesKey2 = getNewEciesPublicKey();
+  EciesAeadHkdfPublicKey eciesKey3 = getNewEciesPublicKey();
 
-  uint32_t key_id_2 = 726329;
-  TINKAddRawKey(keyType, key_id_2, getNewEciesPublicKey(), TINKPBKeyStatusType_Enabled,
-                TINKPBKeyData_KeyMaterialType_AsymmetricPublic, keyset);
+  AddTinkKey(publicKeyType, keyId1, eciesKey1, KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PUBLIC,
+             &publicKeyset);
+  AddRawKey(publicKeyType, keyId2, eciesKey2, KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PUBLIC,
+            &publicKeyset);
+  AddLegacyKey(publicKeyType, keyId3, eciesKey3, KeyStatusType::ENABLED, KeyData::ASYMMETRIC_PUBLIC,
+               &publicKeyset);
 
-  uint32_t key_id_3 = 7213743;
-  TINKAddTinkKey(keyType, key_id_3, getNewEciesPublicKey(), TINKPBKeyStatusType_Enabled,
-                 TINKPBKeyData_KeyMaterialType_AsymmetricPublic, keyset);
-  XCTAssertEqual(keyset.keyArray_Count, 3);
-
-  keyset.primaryKeyId = key_id_3;
-
-  // Initialize the registry.
-  NSError *error = nil;
-  TINKHybridConfig *hybridConfig = [[TINKHybridConfig alloc] initWithError:&error];
-  XCTAssertNotNil(hybridConfig);
-  XCTAssertNil(error);
-
-  XCTAssertTrue([TINKConfig registerConfig:hybridConfig error:&error]);
-  XCTAssertNil(error);
-
-  std::string serializedKeyset = TINKPBSerializeToString(keyset, &error);
-  XCTAssertNil(error);
-
-  google::crypto::tink::Keyset ccKeyset;
-  XCTAssertTrue(ccKeyset.ParseFromString(serializedKeyset));
-
+  publicKeyset.set_primary_key_id(keyId3);
   // Create a KeysetHandle and use it with the factory.
   TINKKeysetHandle *keysetHandle = [[TINKKeysetHandle alloc]
-      initWithCCKeysetHandle:TestKeysetHandle::GetKeysetHandle(ccKeyset)];
+      initWithCCKeysetHandle:TestKeysetHandle::GetKeysetHandle(publicKeyset)];
   XCTAssertNotNil(keysetHandle);
 
   // Get a HybridEncrypt primitive.
-  error = nil;
+  NSError *error = nil;
   id<TINKHybridEncrypt> primitive =
       [TINKHybridEncryptFactory primitiveWithKeysetHandle:keysetHandle error:&error];
-  XCTAssertNotNil(primitive);
   XCTAssertNil(error);
+  XCTAssertNotNil(primitive);
 
   // Test the resulting HybridEncrypt-instance.
   NSData *plaintext = [@"some plaintext" dataUsingEncoding:NSUTF8StringEncoding];
diff --git a/objc/Tests/UnitTests/hybrid/TINKHybridKeyTemplateTest.mm b/objc/Tests/UnitTests/hybrid/TINKHybridKeyTemplateTest.mm
index 1c6e799..27f9038 100644
--- a/objc/Tests/UnitTests/hybrid/TINKHybridKeyTemplateTest.mm
+++ b/objc/Tests/UnitTests/hybrid/TINKHybridKeyTemplateTest.mm
@@ -23,17 +23,16 @@
 #import "objc/TINKAeadKeyTemplate.h"
 #import "objc/TINKKeyTemplate.h"
 #import "objc/core/TINKKeyTemplate_Internal.h"
-#import "objc/util/TINKProtoHelpers.h"
-#import "proto/Common.pbobjc.h"
-#import "proto/EciesAeadHkdf.pbobjc.h"
-#import "proto/Tink.pbobjc.h"
+#include "proto/common.pb.h"
+#include "proto/tink.pb.h"
 
 #include "tink/util/status.h"
 
 @interface TINKHybridKeyTemplateTest : XCTestCase
 @end
 
-static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey";
+static std::string const kTypeURL =
+    "type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey";
 
 @implementation TINKHybridKeyTemplateTest
 
@@ -59,46 +58,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(keyTemplate);
 
-  TINKPBKeyTemplate *objcKeyTemplate = TINKKeyTemplateToObjc(keyTemplate.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(objcKeyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:objcKeyTemplate.typeURL]);
-  XCTAssertEqual(objcKeyTemplate.outputPrefixType, TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBEciesAeadHkdfKeyFormat *keyFormat =
-      [TINKPBEciesAeadHkdfKeyFormat parseFromData:objcKeyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  // EC Point Format
-  XCTAssertEqual(TINKPBEcPointFormat_Uncompressed, keyFormat.params.ecPointFormat);
-
-  // Verify DEM params.
-  XCTAssertTrue(keyFormat.params.hasDemParams);
-  TINKPBEciesAeadDemParams *demParams = keyFormat.params.demParams;
-  error = nil;
-  TINKAeadKeyTemplate *expectedDemTpl =
-      [[TINKAeadKeyTemplate alloc] initWithKeyTemplate:TINKAes128Gcm error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(expectedDemTpl);
-
-  error = nil;
-  TINKPBKeyTemplate *expectedDem = TINKKeyTemplateToObjc(expectedDemTpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(expectedDem);
-
-  XCTAssertTrue(demParams.hasAeadDem);
-  XCTAssertEqual(expectedDem.outputPrefixType, demParams.aeadDem.outputPrefixType);
-  XCTAssertTrue([expectedDem.typeURL isEqualToString:demParams.aeadDem.typeURL]);
-  XCTAssertTrue([expectedDem.value isEqualToData:demParams.aeadDem.value]);
-
-  // Verify KEM params.
-  XCTAssertTrue(keyFormat.params.hasKemParams);
-  TINKPBEciesHkdfKemParams *kemParams = keyFormat.params.kemParams;
-  XCTAssertEqual(TINKPBEllipticCurveType_NistP256, kemParams.curveType);
-  XCTAssertEqual(TINKPBHashType_Sha256, kemParams.hkdfHashType);
-  XCTAssertTrue(kemParams.hkdfSalt.length == 0);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testEciesP256HkdfHmacSha256Aes128CtrHmacSha256 {
@@ -110,47 +72,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(keyTemplate);
 
-  TINKPBKeyTemplate *objcKeyTemplate = TINKKeyTemplateToObjc(keyTemplate.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(objcKeyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:objcKeyTemplate.typeURL]);
-  XCTAssertEqual(objcKeyTemplate.outputPrefixType, TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBEciesAeadHkdfKeyFormat *keyFormat =
-      [TINKPBEciesAeadHkdfKeyFormat parseFromData:objcKeyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  // EC Point Format
-  XCTAssertEqual(TINKPBEcPointFormat_Uncompressed, keyFormat.params.ecPointFormat);
-
-  // Verify DEM params.
-  XCTAssertTrue(keyFormat.params.hasDemParams);
-  TINKPBEciesAeadDemParams *demParams = keyFormat.params.demParams;
-
-  error = nil;
-  TINKAeadKeyTemplate *tpl =
-      [[TINKAeadKeyTemplate alloc] initWithKeyTemplate:TINKAes128CtrHmacSha256 error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(tpl);
-
-  error = nil;
-  TINKPBKeyTemplate *expectedDem = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(expectedDem);
-
-  XCTAssertTrue(demParams.hasAeadDem);
-  XCTAssertEqual(expectedDem.outputPrefixType, demParams.aeadDem.outputPrefixType);
-  XCTAssertTrue([expectedDem.typeURL isEqualToString:demParams.aeadDem.typeURL]);
-  XCTAssertTrue([expectedDem.value isEqualToData:demParams.aeadDem.value]);
-
-  // Verify KEM params.
-  XCTAssertTrue(keyFormat.params.hasKemParams);
-  TINKPBEciesHkdfKemParams *kemParams = keyFormat.params.kemParams;
-  XCTAssertEqual(TINKPBEllipticCurveType_NistP256, kemParams.curveType);
-  XCTAssertEqual(TINKPBHashType_Sha256, kemParams.hkdfHashType);
-  XCTAssertTrue(kemParams.hkdfSalt.length == 0);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 @end
diff --git a/objc/Tests/UnitTests/mac/TINKMacKeyTemplateTest.mm b/objc/Tests/UnitTests/mac/TINKMacKeyTemplateTest.mm
index fda2cb0..f8760b0 100644
--- a/objc/Tests/UnitTests/mac/TINKMacKeyTemplateTest.mm
+++ b/objc/Tests/UnitTests/mac/TINKMacKeyTemplateTest.mm
@@ -22,19 +22,16 @@
 
 #import "objc/TINKKeyTemplate.h"
 #import "objc/core/TINKKeyTemplate_Internal.h"
-#import "objc/util/TINKProtoHelpers.h"
-#import "proto/Common.pbobjc.h"
-#import "proto/AesCmac.pbobjc.h"
-#import "proto/Hmac.pbobjc.h"
-#import "proto/Tink.pbobjc.h"
+#include "proto/common.pb.h"
+#include "proto/tink.pb.h"
 
 #include "tink/util/status.h"
 
 @interface TINKMacKeyTemplateTest : XCTestCase
 @end
 
-static NSString *const kHmacKeyTypeURL = @"type.googleapis.com/google.crypto.tink.HmacKey";
-static NSString *const kAesCmacKeyTypeURL = @"type.googleapis.com/google.crypto.tink.AesCmacKey";
+static std::string const kHmacKeyTypeURL = "type.googleapis.com/google.crypto.tink.HmacKey";
+static std::string const kAesCmacKeyTypeURL = "type.googleapis.com/google.crypto.tink.AesCmacKey";
 
 @implementation TINKMacKeyTemplateTest
 
@@ -59,21 +56,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(keyTemplate);
 
-  TINKPBKeyTemplate *objcKeyTemplate = TINKKeyTemplateToObjc(keyTemplate.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(objcKeyTemplate);
-
-  XCTAssertTrue([kHmacKeyTypeURL isEqualToString:objcKeyTemplate.typeURL]);
-  XCTAssertEqual(objcKeyTemplate.outputPrefixType, TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBHmacKeyFormat *keyFormat =
-      [TINKPBHmacKeyFormat parseFromData:objcKeyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.keySize, 32);
-  XCTAssertEqual(keyFormat.params.tagSize, 16);
-  XCTAssertEqual(keyFormat.params.hash_p, TINKPBHashType_Sha256);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->type_url() == kHmacKeyTypeURL);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testHmac256BittagSha256 {
@@ -84,21 +69,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(keyTemplate);
 
-  TINKPBKeyTemplate *objcKeyTemplate = TINKKeyTemplateToObjc(keyTemplate.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(objcKeyTemplate);
-
-  XCTAssertTrue([kHmacKeyTypeURL isEqualToString:objcKeyTemplate.typeURL]);
-  XCTAssertEqual(objcKeyTemplate.outputPrefixType, TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBHmacKeyFormat *keyFormat =
-      [TINKPBHmacKeyFormat parseFromData:objcKeyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.keySize, 32);
-  XCTAssertEqual(keyFormat.params.tagSize, 32);
-  XCTAssertEqual(keyFormat.params.hash_p, TINKPBHashType_Sha256);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->type_url() == kHmacKeyTypeURL);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testHmac256BittagSha512 {
@@ -109,21 +82,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(keyTemplate);
 
-  TINKPBKeyTemplate *objcKeyTemplate = TINKKeyTemplateToObjc(keyTemplate.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(objcKeyTemplate);
-
-  XCTAssertTrue([kHmacKeyTypeURL isEqualToString:objcKeyTemplate.typeURL]);
-  XCTAssertEqual(objcKeyTemplate.outputPrefixType, TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBHmacKeyFormat *keyFormat = [TINKPBHmacKeyFormat parseFromData:objcKeyTemplate.value
-                                                                error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.keySize, 64);
-  XCTAssertEqual(keyFormat.params.tagSize, 32);
-  XCTAssertEqual(keyFormat.params.hash_p, TINKPBHashType_Sha512);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->type_url() == kHmacKeyTypeURL);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testHmac512BittagSha512 {
@@ -134,21 +95,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(keyTemplate);
 
-  TINKPBKeyTemplate *objcKeyTemplate = TINKKeyTemplateToObjc(keyTemplate.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(objcKeyTemplate);
-
-  XCTAssertTrue([kHmacKeyTypeURL isEqualToString:objcKeyTemplate.typeURL]);
-  XCTAssertEqual(objcKeyTemplate.outputPrefixType, TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBHmacKeyFormat *keyFormat =
-      [TINKPBHmacKeyFormat parseFromData:objcKeyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.keySize, 64);
-  XCTAssertEqual(keyFormat.params.tagSize, 64);
-  XCTAssertEqual(keyFormat.params.hash_p, TINKPBHashType_Sha512);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->type_url() == kHmacKeyTypeURL);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testAesCmac {
@@ -159,20 +108,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(keyTemplate);
 
-  TINKPBKeyTemplate *objcKeyTemplate = TINKKeyTemplateToObjc(keyTemplate.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(objcKeyTemplate);
-
-  XCTAssertTrue([kAesCmacKeyTypeURL isEqualToString:objcKeyTemplate.typeURL]);
-  XCTAssertEqual(objcKeyTemplate.outputPrefixType, TINKPBOutputPrefixType_Tink);
-  error = nil;
-  TINKPBAesCmacKeyFormat *keyFormat =
-      [TINKPBAesCmacKeyFormat parseFromData:objcKeyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.keySize, 32);
-  XCTAssertEqual(keyFormat.params.tagSize, 16);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->type_url() == kAesCmacKeyTypeURL);
+  XCTAssertTrue(keyTemplate.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 @end
diff --git a/objc/Tests/UnitTests/signature/TINKSignatureKeyTemplateTest.mm b/objc/Tests/UnitTests/signature/TINKSignatureKeyTemplateTest.mm
index 4716aa1..6b534dd 100644
--- a/objc/Tests/UnitTests/signature/TINKSignatureKeyTemplateTest.mm
+++ b/objc/Tests/UnitTests/signature/TINKSignatureKeyTemplateTest.mm
@@ -22,26 +22,21 @@
 
 #import "objc/TINKKeyTemplate.h"
 #import "objc/core/TINKKeyTemplate_Internal.h"
-#import "objc/util/TINKProtoHelpers.h"
-#import "proto/Common.pbobjc.h"
-#import "proto/Ecdsa.pbobjc.h"
-#import "proto/Empty.pbobjc.h"
-#import "proto/RsaSsaPkcs1.pbobjc.h"
-#import "proto/RsaSsaPss.pbobjc.h"
-#import "proto/Tink.pbobjc.h"
+#include "proto/common.pb.h"
+#include "proto/tink.pb.h"
 
 #include "tink/util/status.h"
 
 @interface TINKSignatureKeyTemplatesTest : XCTestCase
 @end
 
-static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
-static NSString *const kTypeURLRsaPss =
-    @"type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey";
-static NSString *const kTypeURLRsaPkcs1 =
-    @"type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey";
-static NSString *const kTypeURLEd25519 =
-    @"type.googleapis.com/google.crypto.tink.Ed25519PrivateKey";
+static std::string const kTypeURL = "type.googleapis.com/google.crypto.tink.EcdsaPrivateKey";
+static std::string const kTypeURLRsaPss =
+    "type.googleapis.com/google.crypto.tink.RsaSsaPssPrivateKey";
+static std::string const kTypeURLRsaPkcs1 =
+    "type.googleapis.com/google.crypto.tink.RsaSsaPkcs1PrivateKey";
+static std::string const kTypeURLEd25519 =
+    "type.googleapis.com/google.crypto.tink.Ed25519PrivateKey";
 
 @implementation TINKSignatureKeyTemplatesTest
 
@@ -52,23 +47,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBEcdsaKeyFormat *keyFormat =
-      [TINKPBEcdsaKeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha256);
-  XCTAssertEqual(keyFormat.params.curve, TINKPBEllipticCurveType_NistP256);
-  XCTAssertEqual(keyFormat.params.encoding, TINKPBEcdsaSignatureEncoding_Der);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testEcdsaP256KeyTemplateWithIeeeEncoding {
@@ -78,23 +59,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBEcdsaKeyFormat *keyFormat = [TINKPBEcdsaKeyFormat parseFromData:keyTemplate.value
-                                                                  error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha256);
-  XCTAssertEqual(keyFormat.params.curve, TINKPBEllipticCurveType_NistP256);
-  XCTAssertEqual(keyFormat.params.encoding, TINKPBEcdsaSignatureEncoding_IeeeP1363);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testEcdsaP384KeyTemplateWithDerEncoding {
@@ -104,23 +71,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBEcdsaKeyFormat *keyFormat =
-      [TINKPBEcdsaKeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha512);
-  XCTAssertEqual(keyFormat.params.curve, TINKPBEllipticCurveType_NistP384);
-  XCTAssertEqual(keyFormat.params.encoding, TINKPBEcdsaSignatureEncoding_Der);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testEcdsaP384KeyTemplateWithIeeeEncoding {
@@ -130,23 +83,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBEcdsaKeyFormat *keyFormat = [TINKPBEcdsaKeyFormat parseFromData:keyTemplate.value
-                                                                  error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha512);
-  XCTAssertEqual(keyFormat.params.curve, TINKPBEllipticCurveType_NistP384);
-  XCTAssertEqual(keyFormat.params.encoding, TINKPBEcdsaSignatureEncoding_IeeeP1363);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testEcdsaP521KeyTemplateWithDerEncoding {
@@ -156,23 +95,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBEcdsaKeyFormat *keyFormat =
-      [TINKPBEcdsaKeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha512);
-  XCTAssertEqual(keyFormat.params.curve, TINKPBEllipticCurveType_NistP521);
-  XCTAssertEqual(keyFormat.params.encoding, TINKPBEcdsaSignatureEncoding_Der);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testEcdsaP521KeyTemplateWithIeeeEncoding {
@@ -182,23 +107,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURL isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBEcdsaKeyFormat *keyFormat = [TINKPBEcdsaKeyFormat parseFromData:keyTemplate.value
-                                                                  error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha512);
-  XCTAssertEqual(keyFormat.params.curve, TINKPBEllipticCurveType_NistP521);
-  XCTAssertEqual(keyFormat.params.encoding, TINKPBEcdsaSignatureEncoding_IeeeP1363);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURL);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testInvalidKeyTemplate {
@@ -221,21 +132,10 @@
   XCTAssertNotNil(tpl);
 
   error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
 
-  XCTAssertTrue([kTypeURLRsaPkcs1 isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBRsaSsaPkcs1KeyFormat *keyFormat =
-      [TINKPBRsaSsaPkcs1KeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha256);
-  XCTAssertEqual(keyFormat.modulusSizeInBits, 3072);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURLRsaPkcs1);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testRsaSsaPkcs14096Sha512F4KeyTemplate {
@@ -246,22 +146,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURLRsaPkcs1 isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBRsaSsaPkcs1KeyFormat *keyFormat =
-      [TINKPBRsaSsaPkcs1KeyFormat parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.hashType, TINKPBHashType_Sha512);
-  XCTAssertEqual(keyFormat.modulusSizeInBits, 4096);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURLRsaPkcs1);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testRsaSsaPss3072Sha256F4KeyTemplate {
@@ -272,24 +159,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURLRsaPss isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBRsaSsaPssKeyFormat *keyFormat = [TINKPBRsaSsaPssKeyFormat parseFromData:keyTemplate.value
-                                                                          error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.sigHash, TINKPBHashType_Sha256);
-  XCTAssertEqual(keyFormat.params.mgf1Hash, TINKPBHashType_Sha256);
-  XCTAssertEqual(keyFormat.params.saltLength, 32);
-  XCTAssertEqual(keyFormat.modulusSizeInBits, 3072);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURLRsaPss);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testRsaSsaPss4096Sha512F4KeyTemplate {
@@ -300,24 +172,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURLRsaPss isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBRsaSsaPssKeyFormat *keyFormat = [TINKPBRsaSsaPssKeyFormat parseFromData:keyTemplate.value
-                                                                          error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
-
-  XCTAssertEqual(keyFormat.params.sigHash, TINKPBHashType_Sha512);
-  XCTAssertEqual(keyFormat.params.mgf1Hash, TINKPBHashType_Sha512);
-  XCTAssertEqual(keyFormat.params.saltLength, 64);
-  XCTAssertEqual(keyFormat.modulusSizeInBits, 4096);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURLRsaPss);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 - (void)testEd25519KeyTemplate {
@@ -327,18 +184,9 @@
   XCTAssertNil(error);
   XCTAssertNotNil(tpl);
 
-  error = nil;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(tpl.ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([kTypeURLEd25519 isEqualToString:keyTemplate.typeURL]);
-  XCTAssertTrue(keyTemplate.outputPrefixType == TINKPBOutputPrefixType_Tink);
-
-  error = nil;
-  TINKPBEmpty *keyFormat = [TINKPBEmpty parseFromData:keyTemplate.value error:&error];
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyFormat);
+  XCTAssertTrue(tpl.ccKeyTemplate->type_url() == kTypeURLEd25519);
+  XCTAssertTrue(tpl.ccKeyTemplate->output_prefix_type() ==
+                google::crypto::tink::OutputPrefixType::TINK);
 }
 
 @end
diff --git a/objc/Tests/UnitTests/util/TINKProtoHelpersTest.mm b/objc/Tests/UnitTests/util/TINKProtoHelpersTest.mm
deleted file mode 100644
index bfa8dad..0000000
--- a/objc/Tests/UnitTests/util/TINKProtoHelpersTest.mm
+++ /dev/null
@@ -1,64 +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.
- *
- **************************************************************************
- */
-
-#import "objc/util/TINKProtoHelpers.h"
-
-#import <XCTest/XCTest.h>
-
-#import "proto/Tink.pbobjc.h"
-
-#include "proto/tink.pb.h"
-
-@interface TINKProtoHelpersTest : XCTestCase
-@end
-
-@implementation TINKProtoHelpersTest
-
-- (void)testConvertKeyTemplateToObjc {
-  static NSString *const kTypeURL = @"type.googleapis.com/google.crypto.tink.AesGcmKey";
-
-  NSError *error = nil;
-
-  // Empty KeyTemplate.
-  google::crypto::tink::KeyTemplate emptyKeyTemplate;
-  TINKPBKeyTemplate *keyTemplate = TINKKeyTemplateToObjc(&emptyKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertEqual(keyTemplate.typeURL.length, 0);
-  XCTAssertEqual(keyTemplate.value.length, 0);
-  XCTAssertEqual(keyTemplate.outputPrefixType, TINKPBOutputPrefixType_UnknownPrefix);
-
-  // Prepopulated KeyTemplate.
-  google::crypto::tink::KeyTemplate ccKeyTemplate;
-  ccKeyTemplate.set_type_url("type.googleapis.com/google.crypto.tink.AesGcmKey");
-  ccKeyTemplate.set_value("blah blah");
-  ccKeyTemplate.set_output_prefix_type(google::crypto::tink::OutputPrefixType::TINK);
-
-  keyTemplate = TINKKeyTemplateToObjc(&ccKeyTemplate, &error);
-  XCTAssertNil(error);
-  XCTAssertNotNil(keyTemplate);
-
-  XCTAssertTrue([keyTemplate.typeURL isEqualToString:kTypeURL]);
-  XCTAssertTrue(
-      [keyTemplate.value isEqualToData:[@"blah blah" dataUsingEncoding:NSUTF8StringEncoding]]);
-  XCTAssertEqual((NSInteger)ccKeyTemplate.output_prefix_type(),
-                 (NSInteger)keyTemplate.outputPrefixType);
-}
-
-@end
diff --git a/objc/util/TINKProtoHelpers.h b/objc/util/TINKProtoHelpers.h
deleted file mode 100644
index d8e4daa..0000000
--- a/objc/util/TINKProtoHelpers.h
+++ /dev/null
@@ -1,27 +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.
- *
- **************************************************************************
- */
-
-#import <Foundation/Foundation.h>
-
-#include "proto/tink.pb.h"
-
-@class TINKPBKeyTemplate;
-
-/** Converts a C++ KeyTemplate protobuf to an Obj-C TINKPBKeyTemplate. */
-TINKPBKeyTemplate *TINKKeyTemplateToObjc(google::crypto::tink::KeyTemplate *ccKeyTemplate,
-                                         NSError **error);
diff --git a/objc/util/TINKProtoHelpers.mm b/objc/util/TINKProtoHelpers.mm
deleted file mode 100644
index d56c493..0000000
--- a/objc/util/TINKProtoHelpers.mm
+++ /dev/null
@@ -1,52 +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.
- *
- **************************************************************************
- */
-
-#import "objc/util/TINKProtoHelpers.h"
-
-#import "objc/util/TINKErrors.h"
-#import "objc/util/TINKStrings.h"
-#import "proto/Tink.pbobjc.h"
-
-#include "tink/util/status.h"
-#include "proto/tink.pb.h"
-
-TINKPBKeyTemplate *TINKKeyTemplateToObjc(google::crypto::tink::KeyTemplate *ccKeyTemplate,
-                                         NSError **error) {
-  // Serialize it to std::string.
-  std::string serializedKeyTemplate;
-  if (!ccKeyTemplate->SerializeToString(&serializedKeyTemplate)) {
-    if (error) {
-      *error = TINKStatusToError(crypto::tink::util::Status(
-          crypto::tink::util::error::INVALID_ARGUMENT, "Could not serialize C++ KeyTemplate."));
-    }
-    return nil;
-  }
-
-  // Deserialize it to an Obj-C TINKPBKeyTemplate.
-  NSError *parseError = nil;
-  TINKPBKeyTemplate *keyTemplate =
-      [TINKPBKeyTemplate parseFromData:TINKStringToNSData(serializedKeyTemplate) error:&parseError];
-  if (parseError) {
-    if (error) {
-      *error = parseError;
-    }
-    return nil;
-  }
-
-  return keyTemplate;
-}
diff --git a/objc/util/TINKStrings.h b/objc/util/TINKStrings.h
index 896c89b..6d4b098 100644
--- a/objc/util/TINKStrings.h
+++ b/objc/util/TINKStrings.h
@@ -31,3 +31,6 @@
 
 /** Converts a absl::string_view to NSData. */
 NSData* TINKStringViewToNSData(absl::string_view s);
+
+/** Converts a NSData to a std::string. */
+std::string NSDataToTINKString(NSData* data);
diff --git a/objc/util/TINKStrings.mm b/objc/util/TINKStrings.mm
index ab5880e..e74c0e0 100644
--- a/objc/util/TINKStrings.mm
+++ b/objc/util/TINKStrings.mm
@@ -40,3 +40,6 @@
   return [NSData dataWithBytes:s.data() length:s.size()];
 }
 
+std::string NSDataToTINKString(NSData* data) {
+  return std::string(static_cast<const char*>(data.bytes), data.length);
+}
diff --git a/objc/util/TINKTestHelpers.h b/objc/util/TINKTestHelpers.h
deleted file mode 100644
index 07503c0..0000000
--- a/objc/util/TINKTestHelpers.h
+++ /dev/null
@@ -1,67 +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 "absl/strings/string_view.h"
-
-#import "GPBMessage.h"
-#import "proto/Common.pbobjc.h"
-#import "proto/EciesAeadHkdf.pbobjc.h"
-#import "proto/Tink.pbobjc.h"
-
-TINKPBKeyset *TINKCreateKeyset(TINKPBKeyset_Key *primaryKey, TINKPBKeyset_Key *key1,
-                               TINKPBKeyset_Key *key2);
-
-void TINKAddKey(TINKPBKeyset_Key *key, TINKPBKeyset *keyset);
-
-void TINKAddKey(NSString *keyType, NSUInteger keyId, TINKPBKeyset *keyset);
-
-void TINKAddTinkKey(NSString *keyType,
-                    uint32_t keyID,
-                    GPBMessage *key,
-                    TINKPBKeyStatusType keyStatus,
-                    TINKPBKeyData_KeyMaterialType materialType,
-                    TINKPBKeyset *keyset);
-
-void TINKAddLegacyKey(NSString *keyType,
-                      uint32_t keyID,
-                      GPBMessage *key,
-                      TINKPBKeyStatusType keyStatus,
-                      TINKPBKeyData_KeyMaterialType materialType,
-                      TINKPBKeyset *keyset);
-
-void TINKAddRawKey(NSString *keyType,
-                   uint32_t keyID,
-                   GPBMessage *key,
-                   TINKPBKeyStatusType keyStatus,
-                   TINKPBKeyData_KeyMaterialType materialType,
-                   TINKPBKeyset *keyset);
-
-TINKPBEciesAeadHkdfPrivateKey *TINKGetEciesAesGcmHkdfTestKey(TINKPBEllipticCurveType curveType,
-                                                             TINKPBEcPointFormat ecPointFormat,
-                                                             TINKPBHashType hashType,
-                                                             uint32_t aesGcmKeySize);
-
-TINKPBKeyset_Key *TINKCreateKey(NSString *keyType, uint32_t keyID, GPBMessage *newKey,
-                                TINKPBOutputPrefixType outputPrefix, TINKPBKeyStatusType keyStatus,
-                                TINKPBKeyData_KeyMaterialType materialType);
-
-TINKPBKeyset *TINKCreateKeyset(TINKPBKeyset_Key *primaryKey, TINKPBKeyset_Key *key1,
-                               TINKPBKeyset_Key *key2);
-
-/** Serializes an Obj-C protocol buffer to a C++ std::string. */
-std::string TINKPBSerializeToString(GPBMessage *message, NSError **error);
diff --git a/objc/util/TINKTestHelpers.mm b/objc/util/TINKTestHelpers.mm
deleted file mode 100644
index 8ad5c3f..0000000
--- a/objc/util/TINKTestHelpers.mm
+++ /dev/null
@@ -1,177 +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.
- *
- **************************************************************************
- */
-
-#import "objc/util/TINKTestHelpers.h"
-
-#include "tink/subtle/common_enums.h"
-#include "tink/subtle/subtle_util_boringssl.h"
-
-#import <Foundation/Foundation.h>
-
-#import "GPBMessage.h"
-#import "GPBProtocolBuffers.h"
-#import "objc/util/TINKErrors.h"
-#import "objc/util/TINKStrings.h"
-#import "proto/AesGcm.pbobjc.h"
-#import "proto/Common.pbobjc.h"
-#import "proto/EciesAeadHkdf.pbobjc.h"
-#import "proto/Tink.pbobjc.h"
-
-TINKPBKeyset *TINKCreateKeyset(TINKPBKeyset_Key *primaryKey, TINKPBKeyset_Key *key1,
-                               TINKPBKeyset_Key *key2) {
-  TINKPBKeyset *keyset = [[TINKPBKeyset alloc] init];
-
-  TINKAddKey(primaryKey, keyset);
-  TINKAddKey(key1, keyset);
-  TINKAddKey(key2, keyset);
-
-  keyset.primaryKeyId = [primaryKey keyId];
-  return keyset;
-}
-
-TINKPBKeyset_Key *TINKCreateKey(NSString *keyType, uint32_t keyID, GPBMessage *newKey,
-                                TINKPBOutputPrefixType outputPrefix, TINKPBKeyStatusType keyStatus,
-                                TINKPBKeyData_KeyMaterialType materialType) {
-  TINKPBKeyset_Key *key = [[TINKPBKeyset_Key alloc] init];
-  key.outputPrefixType = outputPrefix;
-  key.keyId = keyID;
-  key.status = keyStatus;
-
-  if (!key.hasKeyData) {
-    key.keyData = [[TINKPBKeyData alloc] init];
-  }
-
-  key.keyData.typeURL = keyType;
-  key.keyData.keyMaterialType = materialType;
-  key.keyData.value = [newKey data];
-  return key;
-}
-
-void TINKAddKey(NSString *keyType, uint32_t keyId, GPBMessage *keyMaterial,
-                TINKPBOutputPrefixType outputPrefix, TINKPBKeyStatusType keyStatus,
-                TINKPBKeyData_KeyMaterialType materialType, TINKPBKeyset *keyset) {
-  TINKPBKeyset_Key *key =
-      TINKCreateKey(keyType, keyId, keyMaterial, outputPrefix, keyStatus, materialType);
-
-  TINKAddKey(key, keyset);
-}
-
-void TINKAddKey(TINKPBKeyset_Key *key, TINKPBKeyset *keyset) {
-  if (!keyset.keyArray) {
-    keyset.keyArray = [[NSMutableArray alloc] init];
-  }
-
-  NSMutableArray<TINKPBKeyset_Key *> *keyArray = [keyset keyArray];
-
-  [keyArray addObject:key];
-}
-
-void TINKAddTinkKey(NSString *keyType,
-                    uint32_t keyID,
-                    GPBMessage *key,
-                    TINKPBKeyStatusType keyStatus,
-                    TINKPBKeyData_KeyMaterialType materialType,
-                    TINKPBKeyset *keyset) {
-  TINKAddKey(keyType, keyID, key, TINKPBOutputPrefixType_Tink, keyStatus, materialType, keyset);
-}
-
-void TINKAddLegacyKey(NSString *keyType,
-                      uint32_t keyID,
-                      GPBMessage *key,
-                      TINKPBKeyStatusType keyStatus,
-                      TINKPBKeyData_KeyMaterialType materialType,
-                      TINKPBKeyset *keyset) {
-  TINKAddKey(keyType, keyID, key, TINKPBOutputPrefixType_Legacy, keyStatus, materialType, keyset);
-}
-
-void TINKAddRawKey(NSString *keyType,
-                   uint32_t keyID,
-                   GPBMessage *key,
-                   TINKPBKeyStatusType keyStatus,
-                   TINKPBKeyData_KeyMaterialType materialType,
-                   TINKPBKeyset *keyset) {
-  TINKAddKey(keyType, keyID, key, TINKPBOutputPrefixType_Raw, keyStatus, materialType, keyset);
-}
-
-TINKPBEciesAeadHkdfPrivateKey *TINKGetEciesAesGcmHkdfTestKey(TINKPBEllipticCurveType curveType,
-                                                             TINKPBEcPointFormat ecPointFormat,
-                                                             TINKPBHashType hashType,
-                                                             uint32_t aesGcmKeySize) {
-  /* TODO(candrian): replace the static_cast below with an explicit translation */
-  auto test_key = crypto::tink::subtle::SubtleUtilBoringSSL::GetNewEcKey(
-                      static_cast<crypto::tink::subtle::EllipticCurveType>(curveType))
-                      .ValueOrDie();
-
-  TINKPBEciesAeadHkdfPrivateKey *eciesKey = [[TINKPBEciesAeadHkdfPrivateKey alloc] init];
-  eciesKey.version = 0;
-  eciesKey.keyValue = TINKStringToNSData(test_key.priv);
-
-  if (!eciesKey.hasPublicKey) {
-    eciesKey.publicKey = [[TINKPBEciesAeadHkdfPublicKey alloc] init];
-  }
-
-  TINKPBEciesAeadHkdfPublicKey *publicKey = eciesKey.publicKey;
-  publicKey.version = 0;
-  publicKey.x = TINKStringToNSData(test_key.pub_x);
-  publicKey.y = TINKStringToNSData(test_key.pub_y);
-
-  if (!publicKey.hasParams) {
-    publicKey.params = [[TINKPBEciesAeadHkdfParams alloc] init];
-  }
-
-  TINKPBEciesAeadHkdfParams *params = publicKey.params;
-  params.ecPointFormat = ecPointFormat;
-
-  if (!params.hasKemParams) {
-    params.kemParams = [[TINKPBEciesHkdfKemParams alloc] init];
-  }
-
-  TINKPBEciesHkdfKemParams *kemParams = params.kemParams;
-  kemParams.curveType = curveType;
-  kemParams.hkdfHashType = hashType;
-
-  TINKPBAesGcmKeyFormat *keyFormat = [[TINKPBAesGcmKeyFormat alloc] init];
-  keyFormat.keySize = 32;
-
-  if (!params.hasDemParams) {
-    params.demParams = [[TINKPBEciesAeadDemParams alloc] init];
-  }
-  TINKPBEciesAeadDemParams *demParams = params.demParams;
-
-  if (!demParams.hasAeadDem) {
-    demParams.aeadDem = [[TINKPBKeyTemplate alloc] init];
-  }
-
-  TINKPBKeyTemplate *aeadDem = demParams.aeadDem;
-  aeadDem.typeURL = @"type.googleapis.com/google.crypto.tink.AesGcmKey";
-  aeadDem.value = [keyFormat data];
-
-  return eciesKey;
-}
-
-std::string TINKPBSerializeToString(GPBMessage *message, NSError **error) {
-  NSData *serializedPB = [message data];
-  if (!serializedPB) {
-    if (error) {
-      *error = TINKStatusToError(crypto::tink::util::Status(
-          crypto::tink::util::error::INVALID_ARGUMENT, "Could not serialize message."));
-    }
-    return std::string("");
-  }
-  return std::string(static_cast<const char *>(serializedPB.bytes), serializedPB.length);
-}
diff --git a/proto/BUILD.bazel b/proto/BUILD.bazel
index 6879a8e..f4a2e1d 100644
--- a/proto/BUILD.bazel
+++ b/proto/BUILD.bazel
@@ -1,209 +1,146 @@
-load("//third_party/rules_protobuf/objc:rules.bzl", "objc_proto_compile")
-load("//tools:objc.bzl", "tink_objc_proto_library")
-load("@com_google_protobuf//:protobuf.bzl", "py_proto_library")
-
 licenses(["notice"])
 
 # -----------------------------------------------
 # common
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "common_proto",
     srcs = [
         "common.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "common_objc_pb",
-    protos = ["common.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # tink
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "tink_proto",
     srcs = [
         "tink.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "tink_objc_pb",
-    protos = ["tink.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # config
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "config_proto",
     srcs = [
         "config.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "config_objc_pb",
-    protos = ["config.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # aes-siv
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_siv_proto",
     srcs = [
         "aes_siv.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "aes_siv_objc_pb",
-    protos = ["aes_siv.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # rsa_ssa_pkcs1
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "rsa_ssa_pkcs1_proto",
     srcs = [
         "rsa_ssa_pkcs1.proto",
     ],
+    visibility = ["//visibility:public"],
     deps = [
         ":common_proto",
     ],
 )
 
-objc_proto_compile(
-    name = "rsa_ssa_pkcs1_objc_pb",
-    protos = ["rsa_ssa_pkcs1.proto"],
-    tags = ["manual"],
-    deps = [":common_objc_pb"],
-)
-
 # -----------------------------------------------
 # rsa_ssa_pss
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "rsa_ssa_pss_proto",
     srcs = [
         "rsa_ssa_pss.proto",
     ],
+    visibility = ["//visibility:public"],
     deps = [
         ":common_proto",
     ],
 )
 
-objc_proto_compile(
-    name = "rsa_ssa_pss_objc_pb",
-    protos = ["rsa_ssa_pss.proto"],
-    tags = ["manual"],
-    deps = [":common_objc_pb"],
-)
-
 # -----------------------------------------------
 # ecdsa
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "ecdsa_proto",
     srcs = [
         "ecdsa.proto",
     ],
+    visibility = ["//visibility:public"],
     deps = [
         ":common_proto",
     ],
 )
 
-objc_proto_compile(
-    name = "ecdsa_objc_pb",
-    protos = ["ecdsa.proto"],
-    tags = ["manual"],
-    deps = [":common_objc_pb"],
-)
-
 # -----------------------------------------------
 # ed25519
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "ed25519_proto",
     srcs = [
         "ed25519.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "ed25519_objc_pb",
-    protos = ["ed25519.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # aes_cmac
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_cmac_proto",
     srcs = [
         "aes_cmac.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "aes_cmac_objc_pb",
-    protos = ["aes_cmac.proto"],
-    tags = ["manual"],
-    deps = [],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # hmac
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "hmac_proto",
     srcs = [
         "hmac.proto",
     ],
+    visibility = ["//visibility:public"],
     deps = [":common_proto"],
 )
 
-objc_proto_compile(
-    name = "hmac_objc_pb",
-    protos = ["hmac.proto"],
-    tags = ["manual"],
-    deps = [":common_objc_pb"],
+# -----------------------------------------------
+# JWS hmac
+# -----------------------------------------------
+proto_library(
+    name = "jws_hmac_proto",
+    srcs = [
+        "jws_hmac.proto",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [":common_proto"],
 )
 
 # -----------------------------------------------
 # aes_ctr
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_ctr_proto",
     srcs = [
         "aes_ctr.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "aes_ctr_objc_pb",
-    protos = ["aes_ctr.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
@@ -211,336 +148,185 @@
 # aes_ctr_hmac_aead
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_ctr_hmac_aead_proto",
     srcs = [
         "aes_ctr_hmac_aead.proto",
     ],
+    visibility = ["//visibility:public"],
     deps = [
         ":aes_ctr_proto",
         ":hmac_proto",
     ],
 )
 
-objc_proto_compile(
-    name = "aes_ctr_hmac_aead_objc_pb",
-    protos = ["aes_ctr_hmac_aead.proto"],
-    tags = ["manual"],
-    deps = [
-        ":aes_ctr_objc_pb",
-        ":hmac_objc_pb",
-    ],
-)
-
 # -----------------------------------------------
 # aes_gcm
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_gcm_proto",
     srcs = [
         "aes_gcm.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "aes_gcm_objc_pb",
-    protos = ["aes_gcm.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # aes_gcm_siv
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_gcm_siv_proto",
     srcs = [
         "aes_gcm_siv.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "aes_gcm_siv_objc_pb",
-    protos = ["aes_gcm_siv.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # aes_ctr_hmac_streaming
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_ctr_hmac_streaming_proto",
     srcs = ["aes_ctr_hmac_streaming.proto"],
+    visibility = ["//visibility:public"],
     deps = [
         ":common_proto",
         ":hmac_proto",
     ],
 )
 
-objc_proto_compile(
-    name = "aes_ctr_hmac_streaming_objc_pb",
-    protos = ["aes_ctr_hmac_streaming.proto"],
-    tags = ["manual"],
-    deps = [
-        ":common_objc_pb",
-        ":hmac_objc_pb",
-    ],
-)
-
 # -----------------------------------------------
 # aes_gcm_hkdf_streaming
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_gcm_hkdf_streaming_proto",
     srcs = ["aes_gcm_hkdf_streaming.proto"],
+    visibility = ["//visibility:public"],
     deps = [":common_proto"],
 )
 
-objc_proto_compile(
-    name = "aes_gcm_hkdf_streaming_objc_pb",
-    protos = ["aes_gcm_hkdf_streaming.proto"],
-    tags = ["manual"],
-    deps = [
-        ":common_objc_pb",
-    ],
-)
-
 # -----------------------------------------------
 # aes_eax
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_eax_proto",
     srcs = [
         "aes_eax.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "aes_eax_objc_pb",
-    protos = ["aes_eax.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # chacha20_poly1305
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "chacha20_poly1305_proto",
     srcs = [
         "chacha20_poly1305.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "chacha20_poly1305_objc_pb",
-    protos = ["chacha20_poly1305.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # kms_aead
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "kms_aead_proto",
     srcs = [
         "kms_aead.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "kms_aead_objc_pb",
-    protos = ["kms_aead.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # kms_envelope
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "kms_envelope_proto",
     srcs = [
         "kms_envelope.proto",
     ],
+    visibility = ["//visibility:public"],
     deps = [":tink_proto"],
 )
 
-objc_proto_compile(
-    name = "kms_envelope_objc_pb",
-    protos = ["kms_envelope.proto"],
-    tags = ["manual"],
-    deps = [
-        ":tink_objc_pb",
-    ],
-)
-
 # -----------------------------------------------
 # ecies_aead_hkdf
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "ecies_aead_hkdf_proto",
     srcs = [
         "ecies_aead_hkdf.proto",
     ],
+    visibility = ["//visibility:public"],
     deps = [
         ":common_proto",
         ":tink_proto",
     ],
 )
 
-objc_proto_compile(
-    name = "ecies_aead_hkdf_objc_pb",
-    protos = ["ecies_aead_hkdf.proto"],
-    tags = ["manual"],
-    deps = [
-        ":common_objc_pb",
-        ":tink_objc_pb",
-    ],
-)
-
 # -----------------------------------------------
 # XChacha20 with Poly1305
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "xchacha20_poly1305_proto",
     srcs = [
         "xchacha20_poly1305.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "xchacha20_poly1305_objc_pb",
-    protos = ["xchacha20_poly1305.proto"],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # Hkdf prf
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "hkdf_prf_proto",
     srcs = [
         "hkdf_prf.proto",
     ],
+    visibility = ["//visibility:public"],
     deps = [":common_proto"],
 )
 
-objc_proto_compile(
-    name = "hkdf_prf_objc_pb",
-    protos = ["hkdf_prf.proto"],
-    tags = ["manual"],
-)
-
 # -----------------------------------------------
 # aes_cmac_prf
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "aes_cmac_prf_proto",
     srcs = [
         "aes_cmac_prf.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "aes_cmac_prf_objc_pb",
-    protos = ["aes_cmac_prf.proto"],
-    tags = ["manual"],
-    deps = [],
+    visibility = ["//visibility:public"],
 )
 
 # -----------------------------------------------
 # hmac_prf
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "hmac_prf_proto",
     srcs = [
         "hmac_prf.proto",
     ],
+    visibility = ["//visibility:public"],
     deps = [":common_proto"],
 )
 
-objc_proto_compile(
-    name = "hmac_prf_objc_pb",
-    protos = ["hmac_prf.proto"],
-    tags = ["manual"],
-    deps = [":common_objc_pb"],
-)
-
-
 # ----------------------------------------------------------------------------
 # prf_based_deriver
 # ----------------------------------------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "prf_based_deriver_proto",
     srcs = ["prf_based_deriver.proto"],
+    visibility = ["//visibility:public"],
     deps = [":tink_proto"],
 )
 
-objc_proto_compile(
-    name = "prf_based_deriver_objc_pb",
-    protos = ["prf_based_deriver.proto"],
-    tags = ["manual"],
-)
-
 # -----------------------------------------------
 # empty
 # -----------------------------------------------
 proto_library(
-    visibility = ["//visibility:public"],
     name = "empty_proto",
     srcs = [
         "empty.proto",
     ],
-)
-
-objc_proto_compile(
-    name = "empty_objc_pb",
-    protos = ["empty.proto"],
-    tags = ["manual"],
-)
-
-# -----------------------------------------------
-# objc library
-# -----------------------------------------------
-tink_objc_proto_library(
-    name = "all_objc_proto",
-    srcs = [
-        ":aes_cmac_objc_pb",
-        ":aes_cmac_prf_objc_pb",
-        ":aes_ctr_hmac_aead_objc_pb",
-        ":aes_ctr_hmac_streaming_objc_pb",
-        ":aes_ctr_objc_pb",
-        ":aes_eax_objc_pb",
-        ":aes_gcm_hkdf_streaming_objc_pb",
-        ":aes_gcm_objc_pb",
-        ":aes_siv_objc_pb",
-        ":chacha20_poly1305_objc_pb",
-        ":common_objc_pb",
-        ":config_objc_pb",
-        ":ecdsa_objc_pb",
-        ":ecies_aead_hkdf_objc_pb",
-        ":ed25519_objc_pb",
-        ":empty_objc_pb",
-        ":hmac_objc_pb",
-        ":hmac_prf_objc_pb",
-        ":kms_aead_objc_pb",
-        ":kms_envelope_objc_pb",
-        ":rsa_ssa_pkcs1_objc_pb",
-        ":rsa_ssa_pss_objc_pb",
-        ":tink_objc_pb",
-        ":xchacha20_poly1305_objc_pb",
-    ],
-    tags = ["manual"],
+    visibility = ["//visibility:public"],
 )
diff --git a/proto/aes_cmac.proto b/proto/aes_cmac.proto
index ee150c4..b214834 100644
--- a/proto/aes_cmac.proto
+++ b/proto/aes_cmac.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_cmac_go_proto";
 
 message AesCmacParams {
diff --git a/proto/aes_cmac_prf.proto b/proto/aes_cmac_prf.proto
index de8f32a..cd75015 100644
--- a/proto/aes_cmac_prf.proto
+++ b/proto/aes_cmac_prf.proto
@@ -18,7 +18,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_cmac_prf_go_proto";
 
 // key_type: type.googleapis.com/google.crypto.tink.AesCmacPrfKey
diff --git a/proto/aes_ctr.proto b/proto/aes_ctr.proto
index 27c9d4e..ecdb256 100644
--- a/proto/aes_ctr.proto
+++ b/proto/aes_ctr.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_ctr_go_proto";
 
 message AesCtrParams {
diff --git a/proto/aes_ctr_hmac_aead.proto b/proto/aes_ctr_hmac_aead.proto
index 065de8c..dcf541d 100644
--- a/proto/aes_ctr_hmac_aead.proto
+++ b/proto/aes_ctr_hmac_aead.proto
@@ -23,7 +23,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_ctr_hmac_aead_go_proto";
 
 message AesCtrHmacAeadKeyFormat {
diff --git a/proto/aes_ctr_hmac_streaming.proto b/proto/aes_ctr_hmac_streaming.proto
index b4a3d94..8425424 100644
--- a/proto/aes_ctr_hmac_streaming.proto
+++ b/proto/aes_ctr_hmac_streaming.proto
@@ -23,7 +23,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_ctr_hmac_streaming_go_proto";
 
 message AesCtrHmacStreamingParams {
diff --git a/proto/aes_eax.proto b/proto/aes_eax.proto
index d84ad2a..c673306 100644
--- a/proto/aes_eax.proto
+++ b/proto/aes_eax.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_eax_go_proto";
 
 // only allowing tag size in bytes = 16
diff --git a/proto/aes_gcm.proto b/proto/aes_gcm.proto
index 2a0c584..ca1483f 100644
--- a/proto/aes_gcm.proto
+++ b/proto/aes_gcm.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_gcm_go_proto";
 
 // only allowing IV size in bytes = 12 and tag size in bytes = 16
diff --git a/proto/aes_gcm_hkdf_streaming.proto b/proto/aes_gcm_hkdf_streaming.proto
index 1189ebc..61fb479 100644
--- a/proto/aes_gcm_hkdf_streaming.proto
+++ b/proto/aes_gcm_hkdf_streaming.proto
@@ -19,11 +19,11 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "proto/common.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_gcm_hkdf_streaming_go_proto";
 
 message AesGcmHkdfStreamingParams {
diff --git a/proto/aes_gcm_siv.proto b/proto/aes_gcm_siv.proto
index 2824a54..d97d191 100644
--- a/proto/aes_gcm_siv.proto
+++ b/proto/aes_gcm_siv.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_gcm_siv_go_proto";
 
 // The only allowed IV size is 12 bytes and tag size is 16 bytes.
diff --git a/proto/aes_siv.proto b/proto/aes_siv.proto
index 6442587..ae4bdfe 100644
--- a/proto/aes_siv.proto
+++ b/proto/aes_siv.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_siv_go_proto";
 
 message AesSivKeyFormat {
diff --git a/proto/chacha20_poly1305.proto b/proto/chacha20_poly1305.proto
index 57ec1ca..2cd6ead 100644
--- a/proto/chacha20_poly1305.proto
+++ b/proto/chacha20_poly1305.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/chacha20_poly1305_go_proto";
 
 message ChaCha20Poly1305KeyFormat {}
diff --git a/proto/common.proto b/proto/common.proto
index 2c640a9..d5862d5 100644
--- a/proto/common.proto
+++ b/proto/common.proto
@@ -21,7 +21,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/common_go_proto";
 
 enum EllipticCurveType {
diff --git a/proto/config.proto b/proto/config.proto
index 4017053..ebbd742 100644
--- a/proto/config.proto
+++ b/proto/config.proto
@@ -21,7 +21,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/config_go_proto";
 
 // An entry that describes a key type to be used with Tink library,
@@ -31,12 +30,12 @@
   // KeyTypeEntry is no longer supported.
   option deprecated = true;
 
-  string primitive_name = 1;       // E.g. “Aead”, “Mac”, ... (case-insensitive)
-  string type_url = 2;             // Name of the key type.
+  string primitive_name = 1;  // E.g. “Aead”, “Mac”, ... (case-insensitive)
+  string type_url = 2;        // Name of the key type.
   uint32 key_manager_version = 3;  // Minimum required version of key manager.
   bool new_key_allowed = 4;        // Can the key manager create new keys?
-  string catalogue_name = 5;    // Catalogue to be queried for key manager,
-                                // e.g. "Tink", "Custom", ... (case-insensitive)
+  string catalogue_name = 5;       // Catalogue to be queried for key manager,
+                              // e.g. "Tink", "Custom", ... (case-insensitive)
 }
 
 // A complete configuration of Tink library: a list of key types
diff --git a/proto/ecdsa.proto b/proto/ecdsa.proto
index e7c9af8..6ba3970 100644
--- a/proto/ecdsa.proto
+++ b/proto/ecdsa.proto
@@ -18,18 +18,18 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "proto/common.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/ecdsa_go_proto";
 
 enum EcdsaSignatureEncoding {
   UNKNOWN_ENCODING = 0;
-  // The 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 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.
   IEEE_P1363 = 1;
   // The signature is encoded using ASN.1
   // (https://tools.ietf.org/html/rfc5480#appendix-A):
diff --git a/proto/ecies_aead_hkdf.proto b/proto/ecies_aead_hkdf.proto
index e0caa83..b73d17d 100644
--- a/proto/ecies_aead_hkdf.proto
+++ b/proto/ecies_aead_hkdf.proto
@@ -18,25 +18,29 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "proto/common.proto";
 import "proto/tink.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/ecies_aead_hkdf_go_proto";
 
 // Protos for keys for ECIES with HKDF and AEAD encryption.
 //
 // These definitions follow loosely ECIES ISO 18033-2 standard
-// (Elliptic Curve Integrated Encryption Scheme, see http://www.shoup.net/iso/std6.pdf),
-// with but with some differences:
-//  * use of HKDF key derivation function (instead of KDF1 and KDF2) enabling the use
-//    of optional parameters to the key derivation function, which strenghten the overall
-//    security and allow for binding the key material to application-specific information
-//    (cf. RFC 5869, https://tools.ietf.org/html/rfc5869)
-//  * use of modern AEAD schemes rather than "manual composition" of symmetric encryption
-//    with message authentication codes (as in DEM1, DEM2, and DEM3 schemes of ISO 18033-2)
+// (Elliptic Curve Integrated Encryption Scheme, see
+// http://www.shoup.net/iso/std6.pdf), with but with some differences:
+//  * use of HKDF key derivation function (instead of KDF1 and KDF2) enabling
+//  the use
+//    of optional parameters to the key derivation function, which strenghten
+//    the overall security and allow for binding the key material to
+//    application-specific information (cf. RFC 5869,
+//    https://tools.ietf.org/html/rfc5869)
+//  * use of modern AEAD schemes rather than "manual composition" of symmetric
+//  encryption
+//    with message authentication codes (as in DEM1, DEM2, and DEM3 schemes of
+//    ISO 18033-2)
 //
 // ECIES-keys represent HybridEncryption resp. HybridDecryption primitives.
 
@@ -55,7 +59,8 @@
 // Parameters of AEAD DEM (Data Encapsulation Mechanism).
 message EciesAeadDemParams {
   // Required.
-  KeyTemplate aead_dem = 2;  // Contains e.g. AesCtrHmacAeadKeyFormat or AesGcmKeyFormat.
+  KeyTemplate aead_dem =
+      2;  // Contains e.g. AesCtrHmacAeadKeyFormat or AesGcmKeyFormat.
 }
 
 message EciesAeadHkdfParams {
@@ -81,8 +86,8 @@
   EciesAeadHkdfParams params = 2;
 
   // Affine coordinates of the public key in bigendian representation.
-  // The public key is a point (x, y) on the curve defined by params.kem_params.curve.
-  // Required.
+  // The public key is a point (x, y) on the curve defined by
+  // params.kem_params.curve. Required.
   bytes x = 3;
   // Required.
   bytes y = 4;
diff --git a/proto/ed25519.proto b/proto/ed25519.proto
index 19b8789..93a5d7d 100644
--- a/proto/ed25519.proto
+++ b/proto/ed25519.proto
@@ -23,7 +23,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/ed25519_go_proto";
 
 message Ed25519KeyFormat {}
diff --git a/proto/empty.proto b/proto/empty.proto
index 757b6e2..33831a9 100644
--- a/proto/empty.proto
+++ b/proto/empty.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/empty_go_proto";
 
 message Empty {}
diff --git a/proto/hkdf_prf.proto b/proto/hkdf_prf.proto
index cfafdc0..7a83050 100644
--- a/proto/hkdf_prf.proto
+++ b/proto/hkdf_prf.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/hkdf_prf_proto";
 
 message HkdfPrfParams {
diff --git a/proto/hmac.proto b/proto/hmac.proto
index 7e07589..2733e51 100644
--- a/proto/hmac.proto
+++ b/proto/hmac.proto
@@ -22,11 +22,10 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/hmac_go_proto";
 
 message HmacParams {
-  HashType hash = 1;    // HashType is an enum.
+  HashType hash = 1;  // HashType is an enum.
   uint32 tag_size = 2;
 }
 
diff --git a/proto/hmac_prf.proto b/proto/hmac_prf.proto
index f074da2..189f303 100644
--- a/proto/hmac_prf.proto
+++ b/proto/hmac_prf.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/hmac_prf_go_proto";
 
 message HmacPrfParams {
diff --git a/proto/jws_hmac.proto b/proto/jws_hmac.proto
new file mode 100644
index 0000000..a3dc296
--- /dev/null
+++ b/proto/jws_hmac.proto
@@ -0,0 +1,36 @@
+// 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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+syntax = "proto3";
+
+package google.crypto.tink;
+
+import "proto/common.proto";
+
+option java_package = "com.google.crypto.tink.proto";
+option java_multiple_files = true;
+option go_package = "github.com/google/tink/proto/jws_hmac_go_proto";
+
+// key_type: type.googleapis.com/google.crypto.tink.JwsHmacKey
+message JwsHmacKey {
+  uint32 version = 1;
+  HashType hash_type = 2;
+  bytes key_value = 3;
+}
+
+message JwsHmacKeyFormat {
+  uint32 version = 1;
+  HashType hash_type = 2;
+  uint32 key_size = 3;
+}
diff --git a/proto/kms_aead.proto b/proto/kms_aead.proto
index b7e077c..e818788 100644
--- a/proto/kms_aead.proto
+++ b/proto/kms_aead.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/kms_aead_go_proto";
 
 message KmsAeadKeyFormat {
diff --git a/proto/kms_envelope.proto b/proto/kms_envelope.proto
index 991f412..fa806e6 100644
--- a/proto/kms_envelope.proto
+++ b/proto/kms_envelope.proto
@@ -22,7 +22,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/kms_envelope_go_proto";
 
 message KmsEnvelopeAeadKeyFormat {
diff --git a/proto/prf_based_deriver.proto b/proto/prf_based_deriver.proto
index 8dfdcd4..074b54d 100644
--- a/proto/prf_based_deriver.proto
+++ b/proto/prf_based_deriver.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/prf_based_deriver_go_proto";
 
 message PrfBasedDeriverParams {
diff --git a/proto/rsa_ssa_pkcs1.proto b/proto/rsa_ssa_pkcs1.proto
index 6a4fa60..961189d 100644
--- a/proto/rsa_ssa_pkcs1.proto
+++ b/proto/rsa_ssa_pkcs1.proto
@@ -19,11 +19,11 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "proto/common.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/rsa_ssa_pkcs1_go_proto";
 
 message RsaSsaPkcs1Params {
diff --git a/proto/rsa_ssa_pss.proto b/proto/rsa_ssa_pss.proto
index ecebeee..8e7903f 100644
--- a/proto/rsa_ssa_pss.proto
+++ b/proto/rsa_ssa_pss.proto
@@ -20,11 +20,11 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "proto/common.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/rsa_ssa_pss_go_proto";
 
 message RsaSsaPssParams {
diff --git a/proto/testing/BUILD.bazel b/proto/testing/BUILD.bazel
new file mode 100644
index 0000000..a6bd574
--- /dev/null
+++ b/proto/testing/BUILD.bazel
@@ -0,0 +1,11 @@
+licenses(["notice"])
+
+package(
+    default_testonly = 1,
+)
+
+proto_library(
+    name = "testing_api_proto",
+    srcs = ["testing_api.proto"],
+    visibility = ["//visibility:public"],
+)
diff --git a/proto/testing/testing_api.proto b/proto/testing/testing_api.proto
new file mode 100644
index 0000000..2c0dde4
--- /dev/null
+++ b/proto/testing/testing_api.proto
@@ -0,0 +1,288 @@
+// 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.
+syntax = "proto3";
+
+package tink_testing_api;
+
+option java_package = "com.google.crypto.tink.proto.testing";
+option java_multiple_files = true;
+
+// Service providing metadata about the server.
+service Metadata {
+  // Returns some server information. A test may use this information to verify
+  // that it is talking to the right server.
+  rpc GetServerInfo(ServerInfoRequest) returns (ServerInfoResponse) {}
+}
+
+message ServerInfoRequest {}
+
+message ServerInfoResponse {
+  string tink_version = 1;  // For example '1.4'
+  string language = 2;      // For example 'cc', 'java', 'go' or 'python'.
+}
+
+// Service for Keyset operations.
+service Keyset {
+  // Generates a new keyset from a template.
+  rpc Generate(KeysetGenerateRequest) returns (KeysetGenerateResponse) {}
+  // Generates a public-key keyset from a private-key keyset.
+  rpc Public(KeysetPublicRequest) returns (KeysetPublicResponse) {}
+  // Converts a Keyset from Binary to Json Format
+  rpc ToJson(KeysetToJsonRequest) returns (KeysetToJsonResponse) {}
+  // Converts a Keyset from Json to Binary Format
+  rpc FromJson(KeysetFromJsonRequest) returns (KeysetFromJsonResponse) {}
+}
+
+message KeysetGenerateRequest {
+  bytes template = 1;  // serialized google.crypto.tink.KeyTemplate.
+}
+
+message KeysetGenerateResponse {
+  oneof result {
+    bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+    string err = 2;
+  }
+}
+
+message KeysetPublicRequest {
+  bytes private_keyset = 1;  // serialized google.crypto.tink.Keyset.
+}
+
+message KeysetPublicResponse {
+  oneof result {
+    bytes public_keyset = 1;  // serialized google.crypto.tink.Keyset.
+    string err = 2;
+  }
+}
+
+message KeysetToJsonRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+}
+
+message KeysetToJsonResponse {
+  oneof result {
+    string json_keyset = 1;
+    string err = 2;
+  }
+}
+
+message KeysetFromJsonRequest {
+  string json_keyset = 1;
+}
+
+message KeysetFromJsonResponse {
+  oneof result {
+    bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+    string err = 2;
+  }
+}
+
+// Service for AEAD encryption and decryption
+service Aead {
+  // Encrypts a plaintext with the provided keyset
+  rpc Encrypt(AeadEncryptRequest) returns (AeadEncryptResponse) {}
+  // Decrypts a ciphertext with the provided keyset
+  rpc Decrypt(AeadDecryptRequest) returns (AeadDecryptResponse) {}
+}
+
+message AeadEncryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes plaintext = 2;
+  bytes associated_data = 3;
+}
+
+message AeadEncryptResponse {
+  oneof result {
+    bytes ciphertext = 1;
+    string err = 2;
+  }
+}
+
+message AeadDecryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes ciphertext = 2;
+  bytes associated_data = 3;
+}
+
+message AeadDecryptResponse {
+  oneof result {
+    bytes plaintext = 1;
+    string err = 2;
+  }
+}
+
+// Service for Deterministic AEAD encryption and decryption
+service DeterministicAead {
+  // Encrypts a plaintext with the provided keyset
+  rpc EncryptDeterministically(DeterministicAeadEncryptRequest)
+      returns (DeterministicAeadEncryptResponse) {}
+  // Decrypts a ciphertext with the provided keyset
+  rpc DecryptDeterministically(DeterministicAeadDecryptRequest)
+    returns (DeterministicAeadDecryptResponse) {
+  }
+}
+
+message DeterministicAeadEncryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes plaintext = 2;
+  bytes associated_data = 3;
+}
+
+message DeterministicAeadEncryptResponse {
+  oneof result {
+    bytes ciphertext = 1;
+    string err = 2;
+  }
+}
+
+message DeterministicAeadDecryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes ciphertext = 2;
+  bytes associated_data = 3;
+}
+
+message DeterministicAeadDecryptResponse {
+  oneof result {
+    bytes plaintext = 1;
+    string err = 2;
+  }
+}
+
+// Service for Streaming AEAD encryption and decryption
+service StreamingAead {
+  // Encrypts a plaintext with the provided keyset
+  rpc Encrypt(StreamingAeadEncryptRequest)
+      returns (StreamingAeadEncryptResponse) {}
+  // Decrypts a ciphertext with the provided keyset
+  rpc Decrypt(StreamingAeadDecryptRequest)
+      returns (StreamingAeadDecryptResponse) {}
+}
+
+message StreamingAeadEncryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes plaintext = 2;
+  bytes associated_data = 3;
+}
+
+message StreamingAeadEncryptResponse {
+  oneof result {
+    bytes ciphertext = 1;
+    string err = 2;
+  }
+}
+
+message StreamingAeadDecryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes ciphertext = 2;
+  bytes associated_data = 3;
+}
+
+message StreamingAeadDecryptResponse {
+  oneof result {
+    bytes plaintext = 1;
+    string err = 2;
+  }
+}
+
+// Service to compute and verify MACs
+service Mac {
+  // Computes a MAC for given data
+  rpc ComputeMac(ComputeMacRequest) returns (ComputeMacResponse) {}
+  // Verifies the validity of the MAC value, no error means success
+  rpc VerifyMac(VerifyMacRequest) returns (VerifyMacResponse) {}
+}
+
+message ComputeMacRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes data = 2;
+}
+
+message ComputeMacResponse {
+  oneof result {
+    bytes mac_value = 1;
+    string err = 2;
+  }
+}
+
+message VerifyMacRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes mac_value = 2;
+  bytes data = 3;
+}
+
+message VerifyMacResponse {
+  string err = 1;
+}
+
+// Service to hybrid encrypt and decrypt
+service Hybrid {
+  // Encrypts plaintext binding context_info to the resulting ciphertext
+  rpc Encrypt(HybridEncryptRequest) returns (HybridEncryptResponse) {}
+  // Decrypts ciphertext verifying the integrity of context_info
+  rpc Decrypt(HybridDecryptRequest) returns (HybridDecryptResponse) {}
+}
+
+message HybridEncryptRequest {
+  bytes public_keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes plaintext = 2;
+  bytes context_info = 3;
+}
+
+message HybridEncryptResponse {
+  oneof result {
+    bytes ciphertext = 1;
+    string err = 2;
+  }
+}
+
+message HybridDecryptRequest {
+  bytes private_keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes ciphertext = 2;
+  bytes context_info = 3;
+}
+
+message HybridDecryptResponse {
+  oneof result {
+    bytes plaintext = 1;
+    string err = 2;
+  }
+}
+
+// Service to sign and verify signatures.
+service Signature {
+  // Computes the signature for data
+  rpc Sign(SignatureSignRequest) returns (SignatureSignResponse) {}
+  // Verifies that signature is a digital signature for data
+  rpc Verify(SignatureVerifyRequest) returns (SignatureVerifyResponse) {}
+}
+
+message SignatureSignRequest {
+  bytes private_keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes data = 2;
+}
+
+message SignatureSignResponse {
+  oneof result {
+    bytes signature = 1;
+    string err = 2;
+  }
+}
+
+message SignatureVerifyRequest {
+  bytes public_keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes signature = 2;
+  bytes data = 3;
+}
+
+message SignatureVerifyResponse {
+  string err = 1;
+}
diff --git a/proto/tink.proto b/proto/tink.proto
index cfc125f..743906d 100644
--- a/proto/tink.proto
+++ b/proto/tink.proto
@@ -21,7 +21,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/tink_go_proto";
 
 // Each instantiation of a Tink primitive is identified by type_url,
@@ -54,7 +53,7 @@
   string type_url = 1;  // in format type.googleapis.com/packagename.messagename
   // Optional.
   // If missing, it means the key type doesn't require a *KeyFormat proto.
-  bytes value = 2;      // contains specific serialized *KeyFormat proto
+  bytes value = 2;  // contains specific serialized *KeyFormat proto
   // Optional.
   // If missing, uses OutputPrefixType.TINK.
   OutputPrefixType output_prefix_type = 3;
@@ -112,13 +111,13 @@
   // Required.
   string type_url = 1;  // In format type.googleapis.com/packagename.messagename
   // Required.
-  bytes value = 2;      // contains specific serialized *Key proto
+  bytes value = 2;  // contains specific serialized *Key proto
   enum KeyMaterialType {
     UNKNOWN_KEYMATERIAL = 0;
     SYMMETRIC = 1;
     ASYMMETRIC_PRIVATE = 2;
     ASYMMETRIC_PUBLIC = 3;
-    REMOTE = 4;         // points to a remote key, i.e., in a KMS.
+    REMOTE = 4;  // points to a remote key, i.e., in a KMS.
   }
   // Required.
   KeyMaterialType key_material_type = 3;
@@ -130,7 +129,6 @@
 // Any given keyset (and any given key) can be used for one primitive only.
 message Keyset {
   message Key {
-
     // Contains the actual, instantiation specific key proto.
     // By convention, each key proto contains a version field.
     KeyData key_data = 1;
diff --git a/proto/xchacha20_poly1305.proto b/proto/xchacha20_poly1305.proto
index 353814c..29d3938 100644
--- a/proto/xchacha20_poly1305.proto
+++ b/proto/xchacha20_poly1305.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/xchacha20_poly1305_go_proto";
 
 message XChaCha20Poly1305KeyFormat {}
diff --git a/python/setup.py b/python/setup.py
index a4513b7..aed66e4 100644
--- a/python/setup.py
+++ b/python/setup.py
@@ -15,17 +15,19 @@
 from __future__ import division
 from __future__ import print_function
 
+from distutils import spawn
 import glob
 import os
 import posixpath
+import re
 import shutil
 import subprocess
 import sys
 
-from distutils import spawn
 import setuptools
 from setuptools.command import build_ext
 
+
 here = os.path.dirname(os.path.abspath(__file__))
 
 
@@ -105,40 +107,56 @@
   Returns:
     The workspace_content using http_archive for tink_base and tink_cc.
   """
-  # Add http_archive load
-  workspace_lines = workspace_content.split('\n')
-  http_archive_load = ('load("@bazel_tools//tools/build_defs/repo:http.bzl", '
-                       '"http_archive")')
-  workspace_content = '\n'.join([workspace_lines[0], http_archive_load] +
-                                workspace_lines[1:])
+  # This is run by pip from a temporary folder which breaks the WORKSPACE paths.
+  # This replaces the paths with the latest http_archive.
+  # In order to override this with a local WORKSPACE use the
+  # TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH environment variable.
 
-  # Replace local with http archives
-  base = ('local_repository(\n'
-          '    name = "tink_base",\n'
-          '    path = "..",\n'
+  if 'TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH' in os.environ:
+    base_path = os.environ['TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH']
+    workspace_content = re.sub(r'(?<="tink_base",\n    path = ").*(?=\n)',
+                               base_path + '",  # Modified by setup.py',
+                               workspace_content)
+    workspace_content = re.sub(r'(?<="tink_cc",\n    path = ").*(?=\n)',
+                               base_path + '/cc' + '",  # Modified by setup.py',
+                               workspace_content)
+  else:
+    # If not base is specified use the latest version from GitHub
+    # Add http_archive load
+    workspace_lines = workspace_content.split('\n')
+    http_archive_load = ('load("@bazel_tools//tools/build_defs/repo:http.bzl", '
+                         '"http_archive")')
+    workspace_content = '\n'.join([workspace_lines[0], http_archive_load] +
+                                  workspace_lines[1:])
+
+    base = ('local_repository(\n'
+            '    name = "tink_base",\n'
+            '    path = "..",\n'
+            ')\n')
+
+    cc = ('local_repository(\n'
+          '    name = "tink_cc",\n'
+          '    path = "../cc",\n'
           ')\n')
 
-  cc = ('local_repository(\n'
-        '    name = "tink_cc",\n'
-        '    path = "../cc",\n'
+    base_patched = (
+        '# Modified by setup.py\n'
+        'http_archive(\n'
+        '    name = "tink_base",\n'
+        '    urls = ["https://github.com/google/tink/archive/master.zip"],\n'
+        '    strip_prefix = "tink-master/",\n'
         ')\n')
 
-  base_http = (
-      'http_archive(\n'
-      '    name = "tink_base",\n'
-      '    urls = ["https://github.com/google/tink/archive/master.zip"],\n'
-      '    strip_prefix = "tink-master/",\n'
-      ')\n')
+    cc_patched = (
+        '# Modified by setup.py\n'
+        'http_archive(\n'
+        '    name = "tink_cc",\n'
+        '    urls = ["https://github.com/google/tink/archive/master.zip"],\n'
+        '    strip_prefix = "tink-master/cc",\n'
+        ')\n')
 
-  cc_http = (
-      'http_archive(\n'
-      '    name = "tink_cc",\n'
-      '    urls = ["https://github.com/google/tink/archive/master.zip"],\n'
-      '    strip_prefix = "tink-master/cc",\n'
-      ')\n')
-
-  workspace_content = workspace_content.replace(base, base_http)
-  workspace_content = workspace_content.replace(cc, cc_http)
+    workspace_content = workspace_content.replace(base, base_patched)
+    workspace_content = workspace_content.replace(cc, cc_patched)
   return workspace_content
 
 
@@ -175,10 +193,11 @@
       os.makedirs(self.build_temp)
 
     bazel_argv = [
-        bazel,
-        'build',
-        ext.bazel_target,
+        bazel, 'build', ext.bazel_target,
         '--compilation_mode=' + ('dbg' if self.debug else 'opt'),
+        '--incompatible_linkopts_to_linklibs'
+        # TODO(https://github.com/bazelbuild/bazel/issues/9254): Remove linkopts
+        # flag when issue is fixed.
     ]
     self.spawn(bazel_argv)
     ext_bazel_bin_path = os.path.join('bazel-bin', ext.relpath,
@@ -212,6 +231,7 @@
     # PyPI package information.
     classifiers=[
         'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
         'Topic :: Software Development :: Libraries',
     ],
     license='Apache 2.0',
diff --git a/python/tink/_keyset_handle.py b/python/tink/_keyset_handle.py
index 2547b97..6cfeafe 100644
--- a/python/tink/_keyset_handle.py
+++ b/python/tink/_keyset_handle.py
@@ -47,6 +47,7 @@
         ('KeysetHandle cannot be instantiated directly.'))
 
   def __init__(self, keyset: tink_pb2.Keyset):
+    _validate_keyset(keyset)
     self._keyset = keyset
 
   @classmethod
diff --git a/python/tink/_keyset_handle_test.py b/python/tink/_keyset_handle_test.py
index 28e0ca8..228ef70 100644
--- a/python/tink/_keyset_handle_test.py
+++ b/python/tink/_keyset_handle_test.py
@@ -202,55 +202,51 @@
         aead_primitive.decrypt(
             aead_primitive.encrypt(b'message', b'aad'), b'aad'), b'message')
 
-  def test_primitive_fails_on_empty_keyset(self):
+  def test_init_fails_on_empty_keyset(self):
     keyset = tink_pb2.Keyset()
     keyset.key.extend([helper.fake_key(key_id=1, status=tink_pb2.DESTROYED)])
     keyset.primary_key_id = 1
-    handle = _keyset_handle(keyset)
     with self.assertRaisesRegex(core.TinkError, 'empty keyset'):
-      handle.primitive(aead.Aead)
+      _ = _keyset_handle(keyset)
 
-  def test_primitive_fails_on_key_without_keydata(self):
+  def test_init_fails_on_key_without_keydata(self):
     keyset = tink_pb2.Keyset()
     key = helper.fake_key(key_id=123)
     key.ClearField('key_data')
     keyset.key.extend([key])
     keyset.primary_key_id = 123
-    handle = _keyset_handle(keyset)
     with self.assertRaisesRegex(core.TinkError, 'key 123 has no key data'):
+      handle = _keyset_handle(keyset)
       handle.primitive(aead.Aead)
 
-  def test_primitive_fails_on_key_with_unknown_prefix(self):
+  def test_init_fails_on_key_with_unknown_prefix(self):
     keyset = tink_pb2.Keyset()
     keyset.key.extend([
         helper.fake_key(key_id=12, output_prefix_type=tink_pb2.UNKNOWN_PREFIX)
     ])
     keyset.primary_key_id = 12
-    handle = _keyset_handle(keyset)
     with self.assertRaisesRegex(core.TinkError, 'key 12 has unknown prefix'):
-      handle.primitive(aead.Aead)
+      _ = _keyset_handle(keyset)
 
-  def test_primitive_fails_on_key_with_unknown_status(self):
+  def test_init_fails_on_key_with_unknown_status(self):
     keyset = tink_pb2.Keyset()
     keyset.key.extend(
         [helper.fake_key(key_id=1234, status=tink_pb2.UNKNOWN_STATUS)])
     keyset.primary_key_id = 1234
-    handle = _keyset_handle(keyset)
     with self.assertRaisesRegex(core.TinkError, 'key 1234 has unknown status'):
-      handle.primitive(aead.Aead)
+      _ = _keyset_handle(keyset)
 
-  def test_primitive_fails_on_multiple_primary_keys(self):
+  def test_init_fails_on_multiple_primary_keys(self):
     keyset = tink_pb2.Keyset()
     keyset.key.extend(
         [helper.fake_key(key_id=12345),
          helper.fake_key(key_id=12345)])
     keyset.primary_key_id = 12345
-    handle = _keyset_handle(keyset)
     with self.assertRaisesRegex(core.TinkError,
                                 'keyset contains multiple primary keys'):
-      handle.primitive(aead.Aead)
+      _ = _keyset_handle(keyset)
 
-  def test_primitive_fails_without_primary_key_present(self):
+  def test_init_fails_without_primary_key_present(self):
     keyset = tink_pb2.Keyset()
     key = keyset.key.add()
     key.key_data.CopyFrom(
@@ -259,10 +255,9 @@
     key.key_id = 2
     key.status = tink_pb2.ENABLED
     keyset.primary_key_id = 1
-    handle = _keyset_handle(keyset)
     with self.assertRaisesRegex(core.TinkError,
                                 'keyset does not contain a valid primary key'):
-      handle.primitive(aead.Aead)
+      _ = _keyset_handle(keyset)
 
   def test_primitive_fails_on_wrong_primitive_class(self):
     keyset = tink_pb2.Keyset()
@@ -300,7 +295,7 @@
             aead_primitive2.encrypt(b'message', b'aad'), b'aad'), b'message')
 
   def test_keyset_info(self):
-    keyset = tink_pb2.Keyset(primary_key_id=2)
+    keyset = tink_pb2.Keyset(primary_key_id=1)
     keyset.key.extend([
         helper.fake_key(
             value=b'v1',
@@ -316,7 +311,7 @@
             output_prefix_type=tink_pb2.RAW)
     ])
     handle = _keyset_handle(keyset)
-    expected_keyset_info = tink_pb2.KeysetInfo(primary_key_id=2)
+    expected_keyset_info = tink_pb2.KeysetInfo(primary_key_id=1)
     info1 = expected_keyset_info.key_info.add()
     info1.type_url = 't1'
     info1.status = tink_pb2.ENABLED
diff --git a/python/tink/_keyset_reader.py b/python/tink/_keyset_reader.py
index 29b901e..63e9672 100644
--- a/python/tink/_keyset_reader.py
+++ b/python/tink/_keyset_reader.py
@@ -20,10 +20,11 @@
 from __future__ import print_function
 
 import abc
+
+from typing import Text
 # Special imports
 import six
 
-from typing import Text
 
 from tink.proto import tink_pb2
 from tink import core
@@ -38,12 +39,12 @@
   @abc.abstractmethod
   def read(self) -> tink_pb2.Keyset:
     """Reads and returns a (cleartext) tink_pb2.Keyset from its source."""
-    pass
+    raise NotImplementedError()
 
   @abc.abstractmethod
   def read_encrypted(self) -> tink_pb2.EncryptedKeyset:
     """Reads and returns an tink_pb2.EncryptedKeyset from its source."""
-    pass
+    raise NotImplementedError()
 
 
 class JsonKeysetReader(KeysetReader):
diff --git a/python/tink/_keyset_reader_test.py b/python/tink/_keyset_reader_test.py
index 8fef028..83fe54d 100644
--- a/python/tink/_keyset_reader_test.py
+++ b/python/tink/_keyset_reader_test.py
@@ -18,9 +18,9 @@
 from __future__ import division
 from __future__ import print_function
 
+from typing import cast
 from absl.testing import absltest
 
-from typing import cast
 from tink.proto import tink_pb2
 import tink
 from tink import core
@@ -102,17 +102,17 @@
     self.assertEqual(keyset, reader.read())
 
   def test_read_none(self):
-    with self.assertRaisesRegex(core.TinkError, 'No keyset found'):
+    with self.assertRaises(core.TinkError):
       reader = tink.BinaryKeysetReader(cast(bytes, None))
       reader.read()
 
   def test_read_empty(self):
-    with self.assertRaisesRegex(core.TinkError, 'No keyset found'):
+    with self.assertRaises(core.TinkError):
       reader = tink.BinaryKeysetReader(b'')
       reader.read()
 
   def test_read_invalid(self):
-    with self.assertRaisesRegex(core.TinkError, 'Wrong wire type'):
+    with self.assertRaises(core.TinkError):
       reader = tink.BinaryKeysetReader(b'some weird data')
       reader.read()
 
@@ -130,17 +130,17 @@
     self.assertEqual(encrypted_keyset, reader.read_encrypted())
 
   def test_read_encrypted_none(self):
-    with self.assertRaisesRegex(core.TinkError, 'No keyset found'):
+    with self.assertRaises(core.TinkError):
       reader = tink.BinaryKeysetReader(cast(bytes, None))
       reader.read_encrypted()
 
   def test_read_encrypted_empty(self):
-    with self.assertRaisesRegex(core.TinkError, 'No keyset found'):
+    with self.assertRaises(core.TinkError):
       reader = tink.BinaryKeysetReader(b'')
       reader.read_encrypted()
 
   def test_read_encrypted_invalid(self):
-    with self.assertRaisesRegex(core.TinkError, 'Wrong wire type'):
+    with self.assertRaises(core.TinkError):
       reader = tink.BinaryKeysetReader(b'some weird data')
       reader.read_encrypted()
 
diff --git a/python/tink/_keyset_writer.py b/python/tink/_keyset_writer.py
index c2b4cd5..b62a152 100644
--- a/python/tink/_keyset_writer.py
+++ b/python/tink/_keyset_writer.py
@@ -21,10 +21,10 @@
 
 import abc
 
+from typing import BinaryIO, TextIO
 # Special imports
 import six
 
-from typing import BinaryIO, TextIO
 from google.protobuf import json_format
 from tink.proto import tink_pb2
 from tink import core
@@ -37,12 +37,12 @@
   @abc.abstractmethod
   def write(self, keyset: tink_pb2.Keyset) -> None:
     """Tries to write a tink_pb2.Keyset to some storage system."""
-    pass
+    raise NotImplementedError()
 
   @abc.abstractmethod
   def write_encrypted(self, encrypted_keyset: tink_pb2.EncryptedKeyset) -> None:
     """Tries to write an tink_pb2.EncryptedKeyset to some storage system."""
-    pass
+    raise NotImplementedError()
 
 
 class JsonKeysetWriter(KeysetWriter):
diff --git a/python/tink/_keyset_writer_test.py b/python/tink/_keyset_writer_test.py
index 89d0397..44cf6e1 100644
--- a/python/tink/_keyset_writer_test.py
+++ b/python/tink/_keyset_writer_test.py
@@ -21,8 +21,9 @@
 
 import io
 
-from absl.testing import absltest
 from typing import cast
+from absl.testing import absltest
+
 from tink.proto import tink_pb2
 import tink
 from tink import core
diff --git a/python/tink/aead/BUILD.bazel b/python/tink/aead/BUILD.bazel
index 32a134c..9b0e9ff 100644
--- a/python/tink/aead/BUILD.bazel
+++ b/python/tink/aead/BUILD.bazel
@@ -46,10 +46,14 @@
     deps = [
         ":aead",
         requirement("absl-py"),
+        "//tink:tink_python",
         "//tink/core",
+        "//tink/proto:aes_ctr_hmac_aead_py_pb2",
         "//tink/proto:aes_eax_py_pb2",
         "//tink/proto:aes_gcm_py_pb2",
+        "//tink/proto:common_py_pb2",
         "//tink/proto:tink_py_pb2",
+        "//tink/proto:xchacha20_poly1305_py_pb2",
     ],
 )
 
diff --git a/python/tink/aead/_aead_key_manager_test.py b/python/tink/aead/_aead_key_manager_test.py
index e0604d4..6301002 100644
--- a/python/tink/aead/_aead_key_manager_test.py
+++ b/python/tink/aead/_aead_key_manager_test.py
@@ -19,9 +19,14 @@
 from __future__ import print_function
 
 from absl.testing import absltest
+from absl.testing import parameterized
+from tink.proto import aes_ctr_hmac_aead_pb2
 from tink.proto import aes_eax_pb2
 from tink.proto import aes_gcm_pb2
+from tink.proto import common_pb2
 from tink.proto import tink_pb2
+from tink.proto import xchacha20_poly1305_pb2
+import tink
 from tink import aead
 from tink import core
 
@@ -30,47 +35,14 @@
   aead.register()
 
 
-class AeadKeyManagerTest(absltest.TestCase):
+class AeadKeyManagerTest(parameterized.TestCase):
 
-  def setUp(self):
-    super(AeadKeyManagerTest, self).setUp()
-    self.key_manager_eax = core.Registry.key_manager(
-        'type.googleapis.com/google.crypto.tink.AesEaxKey')
-    self.key_manager_gcm = core.Registry.key_manager(
-        'type.googleapis.com/google.crypto.tink.AesGcmKey')
-
-  def new_aes_eax_key_template(self, iv_size, key_size):
-    key_format = aes_eax_pb2.AesEaxKeyFormat()
-    key_format.params.iv_size = iv_size
-    key_format.key_size = key_size
-    key_template = tink_pb2.KeyTemplate()
-    key_template.type_url = ('type.googleapis.com/google.crypto.tink.AesEaxKey')
-    key_template.value = key_format.SerializeToString()
-    return key_template
-
-  def new_aes_gcm_key_template(self, key_size):
-    key_format = aes_gcm_pb2.AesGcmKeyFormat()
-    key_format.key_size = key_size
-    key_template = tink_pb2.KeyTemplate()
-    key_template.type_url = ('type.googleapis.com/google.crypto.tink.AesGcmKey')
-    key_template.value = key_format.SerializeToString()
-    return key_template
-
-  def test_primitive_class(self):
-    self.assertEqual(self.key_manager_eax.primitive_class(), aead.Aead)
-    self.assertEqual(self.key_manager_gcm.primitive_class(), aead.Aead)
-
-  def test_key_type(self):
-    self.assertEqual(self.key_manager_eax.key_type(),
-                     'type.googleapis.com/google.crypto.tink.AesEaxKey')
-    self.assertEqual(self.key_manager_gcm.key_type(),
-                     'type.googleapis.com/google.crypto.tink.AesGcmKey')
-
-  def test_new_key_data(self):
-    # AES EAX
-    key_template = self.new_aes_eax_key_template(12, 16)
-    key_data = self.key_manager_eax.new_key_data(key_template)
-    self.assertEqual(key_data.type_url, self.key_manager_eax.key_type())
+  def test_new_key_data_aes_eax(self):
+    key_template = aead.aead_key_templates.create_aes_eax_key_template(
+        key_size=16, iv_size=12)
+    key_manager = core.Registry.key_manager(key_template.type_url)
+    key_data = key_manager.new_key_data(key_template)
+    self.assertEqual(key_data.type_url, key_template.type_url)
     self.assertEqual(key_data.key_material_type, tink_pb2.KeyData.SYMMETRIC)
     key = aes_eax_pb2.AesEaxKey()
     key.ParseFromString(key_data.value)
@@ -78,49 +50,101 @@
     self.assertEqual(key.params.iv_size, 12)
     self.assertLen(key.key_value, 16)
 
-    # AES GCM
-    key_template = self.new_aes_gcm_key_template(16)
-    key_data = self.key_manager_gcm.new_key_data(key_template)
-    self.assertEqual(key_data.type_url, self.key_manager_gcm.key_type())
+  def test_new_key_data_aes_gcm(self):
+    key_template = aead.aead_key_templates.create_aes_gcm_key_template(
+        key_size=16)
+    key_manager = core.Registry.key_manager(key_template.type_url)
+    key_data = key_manager.new_key_data(key_template)
+    self.assertEqual(key_data.type_url, key_template.type_url)
     self.assertEqual(key_data.key_material_type, tink_pb2.KeyData.SYMMETRIC)
     key = aes_gcm_pb2.AesGcmKey()
     key.ParseFromString(key_data.value)
     self.assertEqual(key.version, 0)
     self.assertLen(key.key_value, 16)
 
-  def test_invalid_params_throw_exception(self):
-    key_template = self.new_aes_eax_key_template(9, 16)
-    with self.assertRaisesRegex(core.TinkError, 'Invalid IV size'):
-      self.key_manager_eax.new_key_data(key_template)
+  def test_new_key_data_aes_ctr_hmac_aead(self):
+    template = aead.aead_key_templates.create_aes_ctr_hmac_aead_key_template(
+        aes_key_size=16,
+        iv_size=12,
+        hmac_key_size=32,
+        tag_size=16,
+        hash_type=common_pb2.SHA256)
+    key_manager = core.Registry.key_manager(template.type_url)
+    key_data = key_manager.new_key_data(template)
+    self.assertEqual(key_data.type_url, template.type_url)
+    self.assertEqual(key_data.key_material_type, tink_pb2.KeyData.SYMMETRIC)
+    key = aes_ctr_hmac_aead_pb2.AesCtrHmacAeadKey()
+    key.ParseFromString(key_data.value)
+    self.assertEqual(key.version, 0)
+    self.assertEqual(key.aes_ctr_key.version, 0)
+    self.assertLen(key.aes_ctr_key.key_value, 16)
+    self.assertEqual(key.aes_ctr_key.params.iv_size, 12)
+    self.assertEqual(key.hmac_key.version, 0)
+    self.assertLen(key.hmac_key.key_value, 32)
+    self.assertEqual(key.hmac_key.params.tag_size, 16)
+    self.assertEqual(key.hmac_key.params.hash, common_pb2.SHA256)
 
-    key_template = self.new_aes_gcm_key_template(17)
-    with self.assertRaisesRegex(core.TinkError,
-                                'supported sizes: 16 or 32 bytes'):
-      self.key_manager_gcm.new_key_data(key_template)
+  def test_new_key_data_xchacha20_poly1305(self):
+    template = aead.aead_key_templates.XCHACHA20_POLY1305
+    key_manager = core.Registry.key_manager(template.type_url)
+    key_data = key_manager.new_key_data(template)
+    self.assertEqual(key_data.type_url, template.type_url)
+    self.assertEqual(key_data.key_material_type, tink_pb2.KeyData.SYMMETRIC)
+    key = xchacha20_poly1305_pb2.XChaCha20Poly1305Key()
+    key.ParseFromString(key_data.value)
+    self.assertEqual(key.version, 0)
+    self.assertLen(key.key_value, 32)
 
-  def test_encrypt_decrypt(self):
-    # AES EAX
-    primitive = self.key_manager_eax.primitive(
-        self.key_manager_eax.new_key_data(
-            self.new_aes_eax_key_template(12, 16)))
+  def test_invalid_params_throw_exception_aes_eax(self):
+    template = aead.aead_key_templates.create_aes_eax_key_template(
+        key_size=16, iv_size=9)
+    with self.assertRaises(tink.TinkError):
+      tink.new_keyset_handle(template)
+
+  def test_invalid_params_throw_exception_aes_gcm(self):
+    template = aead.aead_key_templates.create_aes_gcm_key_template(
+        key_size=17)
+    with self.assertRaises(tink.TinkError):
+      tink.new_keyset_handle(template)
+
+  def test_invalid_params_throw_exception_aes_ctr_hmac_aead(self):
+    template = aead.aead_key_templates.create_aes_ctr_hmac_aead_key_template(
+        aes_key_size=42,
+        iv_size=16,
+        hmac_key_size=32,
+        tag_size=32,
+        hash_type=common_pb2.SHA256)
+    with self.assertRaises(tink.TinkError):
+      tink.new_keyset_handle(template)
+
+  @parameterized.parameters([
+      aead.aead_key_templates.AES128_EAX,
+      aead.aead_key_templates.AES256_EAX,
+      aead.aead_key_templates.AES128_GCM,
+      aead.aead_key_templates.AES256_GCM,
+      aead.aead_key_templates.AES128_CTR_HMAC_SHA256,
+      aead.aead_key_templates.AES256_CTR_HMAC_SHA256,
+      aead.aead_key_templates.XCHACHA20_POLY1305])
+  def test_encrypt_decrypt_success(self, template):
+    keyset_handle = tink.new_keyset_handle(template)
+    primitive = keyset_handle.primitive(aead.Aead)
     plaintext = b'plaintext'
     associated_data = b'associated_data'
     ciphertext = primitive.encrypt(plaintext, associated_data)
     self.assertEqual(primitive.decrypt(ciphertext, associated_data), plaintext)
 
-    # AES GCM
-    primitive = self.key_manager_gcm.primitive(
-        self.key_manager_gcm.new_key_data(self.new_aes_gcm_key_template(16)))
-    plaintext = b'plaintext'
-    associated_data = b'associated_data'
-    ciphertext = primitive.encrypt(plaintext, associated_data)
-    self.assertEqual(primitive.decrypt(ciphertext, associated_data), plaintext)
-
-  def test_invalid_decrypt_raises_error(self):
-    primitive = self.key_manager_eax.primitive(
-        self.key_manager_eax.new_key_data(
-            self.new_aes_eax_key_template(12, 16)))
-    with self.assertRaisesRegex(core.TinkError, 'Ciphertext too short'):
+  @parameterized.parameters([
+      aead.aead_key_templates.AES128_EAX,
+      aead.aead_key_templates.AES256_EAX,
+      aead.aead_key_templates.AES128_GCM,
+      aead.aead_key_templates.AES256_GCM,
+      aead.aead_key_templates.AES128_CTR_HMAC_SHA256,
+      aead.aead_key_templates.AES256_CTR_HMAC_SHA256,
+      aead.aead_key_templates.XCHACHA20_POLY1305])
+  def test_invalid_decrypt_raises_error(self, template):
+    keyset_handle = tink.new_keyset_handle(template)
+    primitive = keyset_handle.primitive(aead.Aead)
+    with self.assertRaises(tink.TinkError):
       primitive.decrypt(b'invalid ciphertext', b'ad')
 
 
diff --git a/python/tink/aead/_kms_envelope_aead_test.py b/python/tink/aead/_kms_envelope_aead_test.py
index ef6bfa7..2962670 100644
--- a/python/tink/aead/_kms_envelope_aead_test.py
+++ b/python/tink/aead/_kms_envelope_aead_test.py
@@ -82,6 +82,15 @@
     with self.assertRaises(core.TinkError):
       plaintext = env_aead.decrypt(corrupted_ciphertext, b'some ad')
 
+  def test_ciphertext_too_short(self):
+    key_template = aead.aead_key_templates.AES256_GCM
+    keyset_handle = tink.new_keyset_handle(key_template)
+    remote_aead = keyset_handle.primitive(aead.Aead)
+    env_aead = aead.KmsEnvelopeAead(key_template, remote_aead)
+
+    with self.assertRaises(core.TinkError):
+      env_aead.decrypt(b'foo', b'some ad')
+
   def test_malformed_dek_length(self):
     key_template = aead.aead_key_templates.AES256_GCM
     keyset_handle = tink.new_keyset_handle(key_template)
diff --git a/python/tink/core/_key_manager.py b/python/tink/core/_key_manager.py
index e3e440c..9ad74f5 100644
--- a/python/tink/core/_key_manager.py
+++ b/python/tink/core/_key_manager.py
@@ -19,8 +19,8 @@
 from __future__ import print_function
 
 import abc
-import six
 from typing import Any, Generic, Text, Type, TypeVar
+import six
 
 from tink.proto import tink_pb2
 from tink.core import _tink_error
@@ -50,7 +50,7 @@
   @abc.abstractmethod
   def primitive_class(self) -> Type[P]:
     """The class of the primitive it uses. Used for internal management."""
-    pass
+    raise NotImplementedError()
 
   @abc.abstractmethod
   def primitive(self, key_data: tink_pb2.KeyData) -> P:
@@ -63,12 +63,12 @@
     Raises:
       tink.TinkError if getting the primitive fails.
     """
-    pass
+    raise NotImplementedError()
 
   @abc.abstractmethod
   def key_type(self) -> Text:
     """Returns the type_url identifying the key type handled by this manager."""
-    pass
+    raise NotImplementedError()
 
   @abc.abstractmethod
   def new_key_data(self,
@@ -82,7 +82,7 @@
     Raises:
       tink.TinkError if the key generation fails.
     """
-    pass
+    raise NotImplementedError()
 
   def does_support(self, type_url: Text) -> bool:
     return self.key_type() == type_url
@@ -103,7 +103,7 @@
     Raises:
       tink.TinkError if the key generation fails.
     """
-    pass
+    raise NotImplementedError()
 
 
 class KeyManagerCcToPyWrapper(KeyManager[P]):
diff --git a/python/tink/core/_registry_test.py b/python/tink/core/_registry_test.py
index 1699de5..1ac8e3e 100644
--- a/python/tink/core/_registry_test.py
+++ b/python/tink/core/_registry_test.py
@@ -43,6 +43,12 @@
     return tink_pb2.KeyData(type_url=key_template.type_url)
 
 
+class UnsupportedKeyManager(DummyKeyManager):
+
+  def does_support(self, type_url):
+    return False
+
+
 class DummyPrivateKeyManager(core.PrivateKeyManager):
 
   def __init__(self, type_url):
@@ -120,6 +126,10 @@
     self.reg.register_key_manager(DummyKeyManager('dummy_type_url', aead.Aead))
     self.reg.register_key_manager(DummyKeyManager('dummy_type_url', aead.Aead))
 
+  def test_register_unsupported_key_manager_fails(self):
+    with self.assertRaises(core.TinkError):
+      self.reg.register_key_manager(UnsupportedKeyManager('unsupported'))
+
   def test_key_manager_replace_fails(self):
     self.reg.register_key_manager(DummyKeyManager('dummy_type_url', aead.Aead))
     # Replacing the primitive_class for a type_url not allowed.
diff --git a/python/tink/integration/awskms/BUILD.bazel b/python/tink/integration/awskms/BUILD.bazel
index 8537e63..bb0cf92 100644
--- a/python/tink/integration/awskms/BUILD.bazel
+++ b/python/tink/integration/awskms/BUILD.bazel
@@ -33,6 +33,7 @@
     ],
     deps = [
         ":awskms",
+        "//tink/testing:helper",
         requirement("absl-py"),
     ],
 )
@@ -46,6 +47,7 @@
     ],
     deps = [
         ":awskms",
+        "//tink/testing:helper",
         requirement("absl-py"),
     ],
 )
diff --git a/python/tink/integration/awskms/_aws_kms_aead_test.py b/python/tink/integration/awskms/_aws_kms_aead_test.py
index b08104d..9223aeb 100644
--- a/python/tink/integration/awskms/_aws_kms_aead_test.py
+++ b/python/tink/integration/awskms/_aws_kms_aead_test.py
@@ -22,11 +22,12 @@
 
 from tink import core
 from tink.integration import awskms
+from tink.testing import helper
 
-CREDENTIAL_PATH = os.path.join(os.environ['TEST_SRCDIR'],
-                               'tink_base/testdata/aws_credentials_cc.txt')
-BAD_CREDENTIALS_PATH = os.path.join(
-    os.environ['TEST_SRCDIR'], 'tink_base/testdata/bad_aws_credentials_cc.txt')
+CREDENTIAL_PATH = os.path.join(helper.get_tink_src_path(),
+                               'testdata/aws_credentials_cc.txt')
+BAD_CREDENTIALS_PATH = os.path.join(helper.get_tink_src_path(),
+                                    'testdata/bad_aws_credentials_cc.txt')
 KEY_URI = 'aws-kms://arn:aws:kms:us-east-2:235739564943:key/3ee50705-5a82-4f5b-9753-05c4f473922f'
 BAD_KEY_URI = 'gcp-kms://projects/tink-test-infrastructure/locations/global/keyRings/unit-and-integration-testing/cryptoKeys/aead-key'
 
diff --git a/python/tink/integration/awskms/_aws_kms_client_test.py b/python/tink/integration/awskms/_aws_kms_client_test.py
index 6c8f01b..0f7c150 100644
--- a/python/tink/integration/awskms/_aws_kms_client_test.py
+++ b/python/tink/integration/awskms/_aws_kms_client_test.py
@@ -22,9 +22,10 @@
 
 from tink import core
 from tink.integration import awskms
+from tink.testing import helper
 
-CREDENTIAL_PATH = os.path.join(os.environ['TEST_SRCDIR'],
-                               'tink_base/testdata/aws_credentials_cc.txt')
+CREDENTIAL_PATH = os.path.join(helper.get_tink_src_path(),
+                               'testdata/aws_credentials_cc.txt')
 KEY_URI = 'aws-kms://arn:aws:kms:us-east-2:235739564943:key/3ee50705-5a82-4f5b-9753-05c4f473922f'
 BAD_KEY_URI = 'gcp-kms://projects/tink-test-infrastructure/locations/global/keyRings/unit-and-integration-testing/cryptoKeys/aead-key'
 
diff --git a/python/tink/integration/gcpkms/BUILD.bazel b/python/tink/integration/gcpkms/BUILD.bazel
index 2ac876a..b1e5172 100644
--- a/python/tink/integration/gcpkms/BUILD.bazel
+++ b/python/tink/integration/gcpkms/BUILD.bazel
@@ -33,6 +33,7 @@
     ],
     deps = [
         ":gcpkms",
+        "//tink/testing:helper",
         requirement("absl-py"),
     ]
 )
@@ -47,6 +48,7 @@
     ],
     deps = [
         ":gcpkms",
+        "//tink/testing:helper",
         requirement("absl-py"),
     ]
 )
diff --git a/python/tink/integration/gcpkms/_gcp_kms_aead_test.py b/python/tink/integration/gcpkms/_gcp_kms_aead_test.py
index bc52fad..bdfbe0a 100644
--- a/python/tink/integration/gcpkms/_gcp_kms_aead_test.py
+++ b/python/tink/integration/gcpkms/_gcp_kms_aead_test.py
@@ -23,15 +23,18 @@
 
 from tink import core
 from tink.integration import gcpkms
+from tink.testing import helper
 
-CREDENTIAL_PATH = os.path.join(os.environ['TEST_SRCDIR'],
-                               'tink_base/testdata/credential.json')
+CREDENTIAL_PATH = os.path.join(helper.get_tink_src_path(),
+                               'testdata/credential.json')
 KEY_URI = 'gcp-kms://projects/tink-test-infrastructure/locations/global/keyRings/unit-and-integration-testing/cryptoKeys/aead-key'
+LOCAL_KEY_URI = 'gcp-kms://projects/tink-test-infrastructure/locations/europe-west1/keyRings/unit-and-integration-test/cryptoKeys/aead-key'
 BAD_KEY_URI = 'aws-kms://arn:aws:kms:us-east-2:235739564943:key/3ee50705-5a82-4f5b-9753-05c4f473922f'
 
-# Set root certificates for gRPC
-os.environ['GRPC_DEFAULT_SSL_ROOTS_FILE_PATH'] = os.path.join(
-    os.environ['TEST_SRCDIR'], 'google_root_pem/file/downloaded')
+if 'TEST_SRCDIR' in os.environ:
+  # Set root certificates for gRPC in Bazel Test which are needed on MacOS
+  os.environ['GRPC_DEFAULT_SSL_ROOTS_FILE_PATH'] = os.path.join(
+      os.environ['TEST_SRCDIR'], 'google_root_pem/file/downloaded')
 
 
 class GcpKmsAeadTest(absltest.TestCase):
@@ -49,6 +52,19 @@
     ciphertext = aead.encrypt(plaintext, associated_data)
     self.assertEqual(plaintext, aead.decrypt(ciphertext, associated_data))
 
+  def test_encrypt_decrypt_localized_uri(self):
+    gcp_client = gcpkms.GcpKmsClient(LOCAL_KEY_URI, CREDENTIAL_PATH)
+    aead = gcp_client.get_aead(LOCAL_KEY_URI)
+
+    plaintext = b'helloworld'
+    ciphertext = aead.encrypt(plaintext, b'')
+    self.assertEqual(plaintext, aead.decrypt(ciphertext, b''))
+
+    plaintext = b'hello'
+    associated_data = b'world'
+    ciphertext = aead.encrypt(plaintext, associated_data)
+    self.assertEqual(plaintext, aead.decrypt(ciphertext, associated_data))
+
   def test_encrypt_with_bad_uri(self):
     with self.assertRaises(core.TinkError):
       gcp_client = gcpkms.GcpKmsClient(KEY_URI, CREDENTIAL_PATH)
diff --git a/python/tink/integration/gcpkms/_gcp_kms_client_test.py b/python/tink/integration/gcpkms/_gcp_kms_client_test.py
index db7741c..41bc932 100644
--- a/python/tink/integration/gcpkms/_gcp_kms_client_test.py
+++ b/python/tink/integration/gcpkms/_gcp_kms_client_test.py
@@ -22,9 +22,11 @@
 from absl.testing import absltest
 
 from tink.integration import gcpkms
+from tink.testing import helper
 
-CREDENTIAL_PATH = os.path.join(os.environ['TEST_SRCDIR'],
-                               'tink_base/testdata/credential.json')
+
+CREDENTIAL_PATH = os.path.join(helper.get_tink_src_path(),
+                               'testdata/credential.json')
 
 
 class GcpKmsClientTest(absltest.TestCase):
diff --git a/python/tink/proto/aes_cmac.proto b/python/tink/proto/aes_cmac.proto
index ee150c4..b214834 100644
--- a/python/tink/proto/aes_cmac.proto
+++ b/python/tink/proto/aes_cmac.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_cmac_go_proto";
 
 message AesCmacParams {
diff --git a/python/tink/proto/aes_cmac_prf.proto b/python/tink/proto/aes_cmac_prf.proto
index de8f32a..cd75015 100644
--- a/python/tink/proto/aes_cmac_prf.proto
+++ b/python/tink/proto/aes_cmac_prf.proto
@@ -18,7 +18,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_cmac_prf_go_proto";
 
 // key_type: type.googleapis.com/google.crypto.tink.AesCmacPrfKey
diff --git a/python/tink/proto/aes_ctr.proto b/python/tink/proto/aes_ctr.proto
index 27c9d4e..ecdb256 100644
--- a/python/tink/proto/aes_ctr.proto
+++ b/python/tink/proto/aes_ctr.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_ctr_go_proto";
 
 message AesCtrParams {
diff --git a/python/tink/proto/aes_ctr_hmac_aead.proto b/python/tink/proto/aes_ctr_hmac_aead.proto
index 9af2b7f..bde8649 100644
--- a/python/tink/proto/aes_ctr_hmac_aead.proto
+++ b/python/tink/proto/aes_ctr_hmac_aead.proto
@@ -23,7 +23,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_ctr_hmac_aead_go_proto";
 
 message AesCtrHmacAeadKeyFormat {
diff --git a/python/tink/proto/aes_ctr_hmac_streaming.proto b/python/tink/proto/aes_ctr_hmac_streaming.proto
index 4d60d5e..3963c27 100644
--- a/python/tink/proto/aes_ctr_hmac_streaming.proto
+++ b/python/tink/proto/aes_ctr_hmac_streaming.proto
@@ -23,7 +23,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_ctr_hmac_streaming_go_proto";
 
 message AesCtrHmacStreamingParams {
diff --git a/python/tink/proto/aes_eax.proto b/python/tink/proto/aes_eax.proto
index d84ad2a..c673306 100644
--- a/python/tink/proto/aes_eax.proto
+++ b/python/tink/proto/aes_eax.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_eax_go_proto";
 
 // only allowing tag size in bytes = 16
diff --git a/python/tink/proto/aes_gcm.proto b/python/tink/proto/aes_gcm.proto
index 2a0c584..ca1483f 100644
--- a/python/tink/proto/aes_gcm.proto
+++ b/python/tink/proto/aes_gcm.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_gcm_go_proto";
 
 // only allowing IV size in bytes = 12 and tag size in bytes = 16
diff --git a/python/tink/proto/aes_gcm_hkdf_streaming.proto b/python/tink/proto/aes_gcm_hkdf_streaming.proto
index b339bea..15985f0 100644
--- a/python/tink/proto/aes_gcm_hkdf_streaming.proto
+++ b/python/tink/proto/aes_gcm_hkdf_streaming.proto
@@ -19,11 +19,11 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "tink/proto/common.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_gcm_hkdf_streaming_go_proto";
 
 message AesGcmHkdfStreamingParams {
diff --git a/python/tink/proto/aes_gcm_siv.proto b/python/tink/proto/aes_gcm_siv.proto
index 2824a54..d97d191 100644
--- a/python/tink/proto/aes_gcm_siv.proto
+++ b/python/tink/proto/aes_gcm_siv.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_gcm_siv_go_proto";
 
 // The only allowed IV size is 12 bytes and tag size is 16 bytes.
diff --git a/python/tink/proto/aes_siv.proto b/python/tink/proto/aes_siv.proto
index 6442587..ae4bdfe 100644
--- a/python/tink/proto/aes_siv.proto
+++ b/python/tink/proto/aes_siv.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/aes_siv_go_proto";
 
 message AesSivKeyFormat {
diff --git a/python/tink/proto/chacha20_poly1305.proto b/python/tink/proto/chacha20_poly1305.proto
index 57ec1ca..2cd6ead 100644
--- a/python/tink/proto/chacha20_poly1305.proto
+++ b/python/tink/proto/chacha20_poly1305.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/chacha20_poly1305_go_proto";
 
 message ChaCha20Poly1305KeyFormat {}
diff --git a/python/tink/proto/common.proto b/python/tink/proto/common.proto
index 2c640a9..d5862d5 100644
--- a/python/tink/proto/common.proto
+++ b/python/tink/proto/common.proto
@@ -21,7 +21,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/common_go_proto";
 
 enum EllipticCurveType {
diff --git a/python/tink/proto/config.proto b/python/tink/proto/config.proto
index 4017053..ebbd742 100644
--- a/python/tink/proto/config.proto
+++ b/python/tink/proto/config.proto
@@ -21,7 +21,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/config_go_proto";
 
 // An entry that describes a key type to be used with Tink library,
@@ -31,12 +30,12 @@
   // KeyTypeEntry is no longer supported.
   option deprecated = true;
 
-  string primitive_name = 1;       // E.g. “Aead”, “Mac”, ... (case-insensitive)
-  string type_url = 2;             // Name of the key type.
+  string primitive_name = 1;  // E.g. “Aead”, “Mac”, ... (case-insensitive)
+  string type_url = 2;        // Name of the key type.
   uint32 key_manager_version = 3;  // Minimum required version of key manager.
   bool new_key_allowed = 4;        // Can the key manager create new keys?
-  string catalogue_name = 5;    // Catalogue to be queried for key manager,
-                                // e.g. "Tink", "Custom", ... (case-insensitive)
+  string catalogue_name = 5;       // Catalogue to be queried for key manager,
+                              // e.g. "Tink", "Custom", ... (case-insensitive)
 }
 
 // A complete configuration of Tink library: a list of key types
diff --git a/python/tink/proto/ecdsa.proto b/python/tink/proto/ecdsa.proto
index df80d9f..043a3d8 100644
--- a/python/tink/proto/ecdsa.proto
+++ b/python/tink/proto/ecdsa.proto
@@ -18,18 +18,18 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "tink/proto/common.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/ecdsa_go_proto";
 
 enum EcdsaSignatureEncoding {
   UNKNOWN_ENCODING = 0;
-  // The 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 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.
   IEEE_P1363 = 1;
   // The signature is encoded using ASN.1
   // (https://tools.ietf.org/html/rfc5480#appendix-A):
diff --git a/python/tink/proto/ecies_aead_hkdf.proto b/python/tink/proto/ecies_aead_hkdf.proto
index 7572f69..6cf6b34 100644
--- a/python/tink/proto/ecies_aead_hkdf.proto
+++ b/python/tink/proto/ecies_aead_hkdf.proto
@@ -18,25 +18,29 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "tink/proto/common.proto";
 import "tink/proto/tink.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/ecies_aead_hkdf_go_proto";
 
 // Protos for keys for ECIES with HKDF and AEAD encryption.
 //
 // These definitions follow loosely ECIES ISO 18033-2 standard
-// (Elliptic Curve Integrated Encryption Scheme, see http://www.shoup.net/iso/std6.pdf),
-// with but with some differences:
-//  * use of HKDF key derivation function (instead of KDF1 and KDF2) enabling the use
-//    of optional parameters to the key derivation function, which strenghten the overall
-//    security and allow for binding the key material to application-specific information
-//    (cf. RFC 5869, https://tools.ietf.org/html/rfc5869)
-//  * use of modern AEAD schemes rather than "manual composition" of symmetric encryption
-//    with message authentication codes (as in DEM1, DEM2, and DEM3 schemes of ISO 18033-2)
+// (Elliptic Curve Integrated Encryption Scheme, see
+// http://www.shoup.net/iso/std6.pdf), with but with some differences:
+//  * use of HKDF key derivation function (instead of KDF1 and KDF2) enabling
+//  the use
+//    of optional parameters to the key derivation function, which strenghten
+//    the overall security and allow for binding the key material to
+//    application-specific information (cf. RFC 5869,
+//    https://tools.ietf.org/html/rfc5869)
+//  * use of modern AEAD schemes rather than "manual composition" of symmetric
+//  encryption
+//    with message authentication codes (as in DEM1, DEM2, and DEM3 schemes of
+//    ISO 18033-2)
 //
 // ECIES-keys represent HybridEncryption resp. HybridDecryption primitives.
 
@@ -55,7 +59,8 @@
 // Parameters of AEAD DEM (Data Encapsulation Mechanism).
 message EciesAeadDemParams {
   // Required.
-  KeyTemplate aead_dem = 2;  // Contains e.g. AesCtrHmacAeadKeyFormat or AesGcmKeyFormat.
+  KeyTemplate aead_dem =
+      2;  // Contains e.g. AesCtrHmacAeadKeyFormat or AesGcmKeyFormat.
 }
 
 message EciesAeadHkdfParams {
@@ -81,8 +86,8 @@
   EciesAeadHkdfParams params = 2;
 
   // Affine coordinates of the public key in bigendian representation.
-  // The public key is a point (x, y) on the curve defined by params.kem_params.curve.
-  // Required.
+  // The public key is a point (x, y) on the curve defined by
+  // params.kem_params.curve. Required.
   bytes x = 3;
   // Required.
   bytes y = 4;
diff --git a/python/tink/proto/ed25519.proto b/python/tink/proto/ed25519.proto
index 19b8789..93a5d7d 100644
--- a/python/tink/proto/ed25519.proto
+++ b/python/tink/proto/ed25519.proto
@@ -23,7 +23,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/ed25519_go_proto";
 
 message Ed25519KeyFormat {}
diff --git a/python/tink/proto/empty.proto b/python/tink/proto/empty.proto
index 757b6e2..33831a9 100644
--- a/python/tink/proto/empty.proto
+++ b/python/tink/proto/empty.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/empty_go_proto";
 
 message Empty {}
diff --git a/python/tink/proto/hkdf_prf.proto b/python/tink/proto/hkdf_prf.proto
index 7bd31be..394f7b3 100644
--- a/python/tink/proto/hkdf_prf.proto
+++ b/python/tink/proto/hkdf_prf.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/hkdf_prf_proto";
 
 message HkdfPrfParams {
diff --git a/python/tink/proto/hmac.proto b/python/tink/proto/hmac.proto
index 8974f64..9395845 100644
--- a/python/tink/proto/hmac.proto
+++ b/python/tink/proto/hmac.proto
@@ -22,11 +22,10 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/hmac_go_proto";
 
 message HmacParams {
-  HashType hash = 1;    // HashType is an enum.
+  HashType hash = 1;  // HashType is an enum.
   uint32 tag_size = 2;
 }
 
diff --git a/python/tink/proto/hmac_prf.proto b/python/tink/proto/hmac_prf.proto
index 5f2c4cd..770b4f9 100644
--- a/python/tink/proto/hmac_prf.proto
+++ b/python/tink/proto/hmac_prf.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/hmac_prf_go_proto";
 
 message HmacPrfParams {
diff --git a/python/tink/proto/jws_hmac.proto b/python/tink/proto/jws_hmac.proto
new file mode 100644
index 0000000..2c4a698
--- /dev/null
+++ b/python/tink/proto/jws_hmac.proto
@@ -0,0 +1,36 @@
+// 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.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+syntax = "proto3";
+
+package google.crypto.tink;
+
+import "tink/proto/common.proto";
+
+option java_package = "com.google.crypto.tink.proto";
+option java_multiple_files = true;
+option go_package = "github.com/google/tink/proto/jws_hmac_go_proto";
+
+// key_type: type.googleapis.com/google.crypto.tink.JwsHmacKey
+message JwsHmacKey {
+  uint32 version = 1;
+  HashType hash_type = 2;
+  bytes key_value = 3;
+}
+
+message JwsHmacKeyFormat {
+  uint32 version = 1;
+  HashType hash_type = 2;
+  uint32 key_size = 3;
+}
diff --git a/python/tink/proto/kms_aead.proto b/python/tink/proto/kms_aead.proto
index b7e077c..e818788 100644
--- a/python/tink/proto/kms_aead.proto
+++ b/python/tink/proto/kms_aead.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/kms_aead_go_proto";
 
 message KmsAeadKeyFormat {
diff --git a/python/tink/proto/kms_envelope.proto b/python/tink/proto/kms_envelope.proto
index 1752b5b..17888d9 100644
--- a/python/tink/proto/kms_envelope.proto
+++ b/python/tink/proto/kms_envelope.proto
@@ -22,7 +22,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/kms_envelope_go_proto";
 
 message KmsEnvelopeAeadKeyFormat {
diff --git a/python/tink/proto/prf_based_deriver.proto b/python/tink/proto/prf_based_deriver.proto
index 92fe552..110c92e 100644
--- a/python/tink/proto/prf_based_deriver.proto
+++ b/python/tink/proto/prf_based_deriver.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/prf_based_deriver_go_proto";
 
 message PrfBasedDeriverParams {
diff --git a/python/tink/proto/rsa_ssa_pkcs1.proto b/python/tink/proto/rsa_ssa_pkcs1.proto
index 3ff3394..fd5fd4e 100644
--- a/python/tink/proto/rsa_ssa_pkcs1.proto
+++ b/python/tink/proto/rsa_ssa_pkcs1.proto
@@ -19,11 +19,11 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "tink/proto/common.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/rsa_ssa_pkcs1_go_proto";
 
 message RsaSsaPkcs1Params {
diff --git a/python/tink/proto/rsa_ssa_pss.proto b/python/tink/proto/rsa_ssa_pss.proto
index e819f23..a4817c0 100644
--- a/python/tink/proto/rsa_ssa_pss.proto
+++ b/python/tink/proto/rsa_ssa_pss.proto
@@ -20,11 +20,11 @@
 syntax = "proto3";
 
 package google.crypto.tink;
+
 import "tink/proto/common.proto";
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/rsa_ssa_pss_go_proto";
 
 message RsaSsaPssParams {
diff --git a/python/tink/proto/testing/testing_api.proto b/python/tink/proto/testing/testing_api.proto
new file mode 100644
index 0000000..2c0dde4
--- /dev/null
+++ b/python/tink/proto/testing/testing_api.proto
@@ -0,0 +1,288 @@
+// 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.
+syntax = "proto3";
+
+package tink_testing_api;
+
+option java_package = "com.google.crypto.tink.proto.testing";
+option java_multiple_files = true;
+
+// Service providing metadata about the server.
+service Metadata {
+  // Returns some server information. A test may use this information to verify
+  // that it is talking to the right server.
+  rpc GetServerInfo(ServerInfoRequest) returns (ServerInfoResponse) {}
+}
+
+message ServerInfoRequest {}
+
+message ServerInfoResponse {
+  string tink_version = 1;  // For example '1.4'
+  string language = 2;      // For example 'cc', 'java', 'go' or 'python'.
+}
+
+// Service for Keyset operations.
+service Keyset {
+  // Generates a new keyset from a template.
+  rpc Generate(KeysetGenerateRequest) returns (KeysetGenerateResponse) {}
+  // Generates a public-key keyset from a private-key keyset.
+  rpc Public(KeysetPublicRequest) returns (KeysetPublicResponse) {}
+  // Converts a Keyset from Binary to Json Format
+  rpc ToJson(KeysetToJsonRequest) returns (KeysetToJsonResponse) {}
+  // Converts a Keyset from Json to Binary Format
+  rpc FromJson(KeysetFromJsonRequest) returns (KeysetFromJsonResponse) {}
+}
+
+message KeysetGenerateRequest {
+  bytes template = 1;  // serialized google.crypto.tink.KeyTemplate.
+}
+
+message KeysetGenerateResponse {
+  oneof result {
+    bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+    string err = 2;
+  }
+}
+
+message KeysetPublicRequest {
+  bytes private_keyset = 1;  // serialized google.crypto.tink.Keyset.
+}
+
+message KeysetPublicResponse {
+  oneof result {
+    bytes public_keyset = 1;  // serialized google.crypto.tink.Keyset.
+    string err = 2;
+  }
+}
+
+message KeysetToJsonRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+}
+
+message KeysetToJsonResponse {
+  oneof result {
+    string json_keyset = 1;
+    string err = 2;
+  }
+}
+
+message KeysetFromJsonRequest {
+  string json_keyset = 1;
+}
+
+message KeysetFromJsonResponse {
+  oneof result {
+    bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+    string err = 2;
+  }
+}
+
+// Service for AEAD encryption and decryption
+service Aead {
+  // Encrypts a plaintext with the provided keyset
+  rpc Encrypt(AeadEncryptRequest) returns (AeadEncryptResponse) {}
+  // Decrypts a ciphertext with the provided keyset
+  rpc Decrypt(AeadDecryptRequest) returns (AeadDecryptResponse) {}
+}
+
+message AeadEncryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes plaintext = 2;
+  bytes associated_data = 3;
+}
+
+message AeadEncryptResponse {
+  oneof result {
+    bytes ciphertext = 1;
+    string err = 2;
+  }
+}
+
+message AeadDecryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes ciphertext = 2;
+  bytes associated_data = 3;
+}
+
+message AeadDecryptResponse {
+  oneof result {
+    bytes plaintext = 1;
+    string err = 2;
+  }
+}
+
+// Service for Deterministic AEAD encryption and decryption
+service DeterministicAead {
+  // Encrypts a plaintext with the provided keyset
+  rpc EncryptDeterministically(DeterministicAeadEncryptRequest)
+      returns (DeterministicAeadEncryptResponse) {}
+  // Decrypts a ciphertext with the provided keyset
+  rpc DecryptDeterministically(DeterministicAeadDecryptRequest)
+    returns (DeterministicAeadDecryptResponse) {
+  }
+}
+
+message DeterministicAeadEncryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes plaintext = 2;
+  bytes associated_data = 3;
+}
+
+message DeterministicAeadEncryptResponse {
+  oneof result {
+    bytes ciphertext = 1;
+    string err = 2;
+  }
+}
+
+message DeterministicAeadDecryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes ciphertext = 2;
+  bytes associated_data = 3;
+}
+
+message DeterministicAeadDecryptResponse {
+  oneof result {
+    bytes plaintext = 1;
+    string err = 2;
+  }
+}
+
+// Service for Streaming AEAD encryption and decryption
+service StreamingAead {
+  // Encrypts a plaintext with the provided keyset
+  rpc Encrypt(StreamingAeadEncryptRequest)
+      returns (StreamingAeadEncryptResponse) {}
+  // Decrypts a ciphertext with the provided keyset
+  rpc Decrypt(StreamingAeadDecryptRequest)
+      returns (StreamingAeadDecryptResponse) {}
+}
+
+message StreamingAeadEncryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes plaintext = 2;
+  bytes associated_data = 3;
+}
+
+message StreamingAeadEncryptResponse {
+  oneof result {
+    bytes ciphertext = 1;
+    string err = 2;
+  }
+}
+
+message StreamingAeadDecryptRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes ciphertext = 2;
+  bytes associated_data = 3;
+}
+
+message StreamingAeadDecryptResponse {
+  oneof result {
+    bytes plaintext = 1;
+    string err = 2;
+  }
+}
+
+// Service to compute and verify MACs
+service Mac {
+  // Computes a MAC for given data
+  rpc ComputeMac(ComputeMacRequest) returns (ComputeMacResponse) {}
+  // Verifies the validity of the MAC value, no error means success
+  rpc VerifyMac(VerifyMacRequest) returns (VerifyMacResponse) {}
+}
+
+message ComputeMacRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes data = 2;
+}
+
+message ComputeMacResponse {
+  oneof result {
+    bytes mac_value = 1;
+    string err = 2;
+  }
+}
+
+message VerifyMacRequest {
+  bytes keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes mac_value = 2;
+  bytes data = 3;
+}
+
+message VerifyMacResponse {
+  string err = 1;
+}
+
+// Service to hybrid encrypt and decrypt
+service Hybrid {
+  // Encrypts plaintext binding context_info to the resulting ciphertext
+  rpc Encrypt(HybridEncryptRequest) returns (HybridEncryptResponse) {}
+  // Decrypts ciphertext verifying the integrity of context_info
+  rpc Decrypt(HybridDecryptRequest) returns (HybridDecryptResponse) {}
+}
+
+message HybridEncryptRequest {
+  bytes public_keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes plaintext = 2;
+  bytes context_info = 3;
+}
+
+message HybridEncryptResponse {
+  oneof result {
+    bytes ciphertext = 1;
+    string err = 2;
+  }
+}
+
+message HybridDecryptRequest {
+  bytes private_keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes ciphertext = 2;
+  bytes context_info = 3;
+}
+
+message HybridDecryptResponse {
+  oneof result {
+    bytes plaintext = 1;
+    string err = 2;
+  }
+}
+
+// Service to sign and verify signatures.
+service Signature {
+  // Computes the signature for data
+  rpc Sign(SignatureSignRequest) returns (SignatureSignResponse) {}
+  // Verifies that signature is a digital signature for data
+  rpc Verify(SignatureVerifyRequest) returns (SignatureVerifyResponse) {}
+}
+
+message SignatureSignRequest {
+  bytes private_keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes data = 2;
+}
+
+message SignatureSignResponse {
+  oneof result {
+    bytes signature = 1;
+    string err = 2;
+  }
+}
+
+message SignatureVerifyRequest {
+  bytes public_keyset = 1;  // serialized google.crypto.tink.Keyset.
+  bytes signature = 2;
+  bytes data = 3;
+}
+
+message SignatureVerifyResponse {
+  string err = 1;
+}
diff --git a/python/tink/proto/tink.proto b/python/tink/proto/tink.proto
index cfc125f..743906d 100644
--- a/python/tink/proto/tink.proto
+++ b/python/tink/proto/tink.proto
@@ -21,7 +21,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/tink_go_proto";
 
 // Each instantiation of a Tink primitive is identified by type_url,
@@ -54,7 +53,7 @@
   string type_url = 1;  // in format type.googleapis.com/packagename.messagename
   // Optional.
   // If missing, it means the key type doesn't require a *KeyFormat proto.
-  bytes value = 2;      // contains specific serialized *KeyFormat proto
+  bytes value = 2;  // contains specific serialized *KeyFormat proto
   // Optional.
   // If missing, uses OutputPrefixType.TINK.
   OutputPrefixType output_prefix_type = 3;
@@ -112,13 +111,13 @@
   // Required.
   string type_url = 1;  // In format type.googleapis.com/packagename.messagename
   // Required.
-  bytes value = 2;      // contains specific serialized *Key proto
+  bytes value = 2;  // contains specific serialized *Key proto
   enum KeyMaterialType {
     UNKNOWN_KEYMATERIAL = 0;
     SYMMETRIC = 1;
     ASYMMETRIC_PRIVATE = 2;
     ASYMMETRIC_PUBLIC = 3;
-    REMOTE = 4;         // points to a remote key, i.e., in a KMS.
+    REMOTE = 4;  // points to a remote key, i.e., in a KMS.
   }
   // Required.
   KeyMaterialType key_material_type = 3;
@@ -130,7 +129,6 @@
 // Any given keyset (and any given key) can be used for one primitive only.
 message Keyset {
   message Key {
-
     // Contains the actual, instantiation specific key proto.
     // By convention, each key proto contains a version field.
     KeyData key_data = 1;
diff --git a/python/tink/proto/xchacha20_poly1305.proto b/python/tink/proto/xchacha20_poly1305.proto
index 353814c..29d3938 100644
--- a/python/tink/proto/xchacha20_poly1305.proto
+++ b/python/tink/proto/xchacha20_poly1305.proto
@@ -20,7 +20,6 @@
 
 option java_package = "com.google.crypto.tink.proto";
 option java_multiple_files = true;
-option objc_class_prefix = "TINKPB";
 option go_package = "github.com/google/tink/proto/xchacha20_poly1305_go_proto";
 
 message XChaCha20Poly1305KeyFormat {}
diff --git a/python/tink/streaming_aead/BUILD.bazel b/python/tink/streaming_aead/BUILD.bazel
index dbbc344..43de0b5 100644
--- a/python/tink/streaming_aead/BUILD.bazel
+++ b/python/tink/streaming_aead/BUILD.bazel
@@ -9,6 +9,7 @@
     name = "streaming_aead",
     srcs = ["__init__.py"],
     srcs_version = "PY3",
+    visibility = ["//visibility:public"],
     deps = [
         ":_decrypting_stream",
         ":_encrypting_stream",
diff --git a/python/tink/streaming_aead/__init__.py b/python/tink/streaming_aead/__init__.py
index a7d2e3a..995f7ae 100644
--- a/python/tink/streaming_aead/__init__.py
+++ b/python/tink/streaming_aead/__init__.py
@@ -27,4 +27,4 @@
 DecryptingStream = _decrypting_stream.DecryptingStream
 EncryptingStream = _encrypting_stream.EncryptingStream
 key_manager_from_cc_registry = _streaming_aead_key_manager.from_cc_registry
-
+register = _streaming_aead_key_manager.register
diff --git a/python/tink/streaming_aead/_streaming_aead_key_manager.py b/python/tink/streaming_aead/_streaming_aead_key_manager.py
index 9314edd..9daf5ec 100644
--- a/python/tink/streaming_aead/_streaming_aead_key_manager.py
+++ b/python/tink/streaming_aead/_streaming_aead_key_manager.py
@@ -54,3 +54,17 @@
   return core.KeyManagerCcToPyWrapper(
       tink_bindings.StreamingAeadKeyManager.from_cc_registry(type_url),
       _streaming_aead.StreamingAead, _StreamingAeadCcToPyWrapper)
+
+
+def register() -> None:
+  """Registers all AEAD key managers and AEAD wrapper in the Registry."""
+  tink_bindings.register()
+  for ident in (
+      'AesCtrHmacStreamingKey',
+      'AesGcmHkdfStreamingKey',
+  ):
+    type_url = 'type.googleapis.com/google.crypto.tink.{}'.format(ident)
+    key_manager = core.KeyManagerCcToPyWrapper(
+        tink_bindings.StreamingAeadKeyManager.from_cc_registry(type_url),
+        _streaming_aead.StreamingAead, _StreamingAeadCcToPyWrapper)
+    core.Registry.register_key_manager(key_manager, new_key_allowed=True)
diff --git a/python/tink/testing/__init__.py b/python/tink/testing/__init__.py
new file mode 100644
index 0000000..824dd6c
--- /dev/null
+++ b/python/tink/testing/__init__.py
@@ -0,0 +1,11 @@
+# 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.
diff --git a/python/tink/testing/helper.py b/python/tink/testing/helper.py
index e13691d..dd28580 100644
--- a/python/tink/testing/helper.py
+++ b/python/tink/testing/helper.py
@@ -19,6 +19,7 @@
 # Placeholder for import for type annotations
 from __future__ import print_function
 
+import os
 from typing import Text
 
 from tink.proto import tink_pb2
@@ -30,6 +31,24 @@
 from tink import signature as pk_signature
 
 
+def get_tink_src_path():
+  """Returns the path to the Tink root directory used for the test enviroment.
+
+     The path must be set in the TINK_SRC_PATH enviroment variable. If Bazel
+     is used the path is derived from the Bazel enviroment variables.
+  """
+  if 'TINK_SRC_PATH' in os.environ:
+    return os.environ['TINK_SRC_PATH']
+  elif 'TEST_SRCDIR' in os.environ:
+    # Bazel enviroment
+    return os.path.join(os.environ['TEST_SRCDIR'], 'tink_base')
+  else:
+    raise ValueError(
+        'Could not find path to Tink root directory. Make sure that '
+        'TINK_SRC_PATH is set.'
+    )
+
+
 def fake_key(value: bytes = b'fakevalue',
              type_url: Text = 'fakeurl',
              key_material_type: tink_pb2.KeyData.KeyMaterialType = tink_pb2
diff --git a/python/tink/util/__init__.py b/python/tink/util/__init__.py
new file mode 100644
index 0000000..824dd6c
--- /dev/null
+++ b/python/tink/util/__init__.py
@@ -0,0 +1,11 @@
+# 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.
diff --git a/python/tink/util/file_object_adapter_test.py b/python/tink/util/file_object_adapter_test.py
index 41ab5e5..4c10815 100644
--- a/python/tink/util/file_object_adapter_test.py
+++ b/python/tink/util/file_object_adapter_test.py
@@ -106,6 +106,12 @@
 
     self.assertEqual(adapter.read(0), b'')
 
+  def test_read_negative_size_fails(self):
+    file_object = io.BytesIO(b'something')
+    adapter = file_object_adapter.FileObjectAdapter(file_object)
+    with self.assertRaises(ValueError):
+      adapter.read(-1)
+
   def test_read_raises_blocking_error(self):
     file_object = mock.Mock()
     file_object.read = mock.Mock(side_effect=io.BlockingIOError(None, None))
diff --git a/python/tools/distribution/README.md b/python/tools/distribution/README.md
new file mode 100644
index 0000000..e199c14
--- /dev/null
+++ b/python/tools/distribution/README.md
@@ -0,0 +1,56 @@
+# Overview
+
+This folder contains scripts to build binary and source wheels of the Tink
+Python package.
+
+## Building the release
+
+In order to generate a release run `./tools/distribution/create_release.sh` from
+the `python/` folder. Note that this requires [https://www.docker.com](Docker)
+to be installed, as it makes use of the
+[https://github.com/pypa/manylinux](pypa images) to build
+[https://www.python.org/dev/peps/pep-0599/](PEP 599) conform wheels.
+
+This will carry out the following three steps:
+
+*   Create binary wheels in a Docker container for Linux.
+*   Create a source distribution of the Python package.
+*   Run automatic tests against the packages created.
+
+The resulting packages of this process will be stored in `python/release` and
+can be distributed. The binary wheels can be installed on Linux without
+Bazel/protoc being available. Currently this supports building binary wheels
+for:
+
+*   manylinux2014_x86_64: Python 3.7, 3.8
+
+The binary wheels are tested inside a Docker container with the corresponding
+Python versions.
+
+The source distribution still needs to compile the C++ bindings, which requires
+Bazel, protoc and the Tink repository to be available. The path to the Tink
+repository can be set with `TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH`. The
+source distribution is tested on the machine which the script is run.
+
+## Publishing the release
+
+The output generated in the previous step can directly be used for upload to
+PyPI. It is recommended to first upload it to the
+[https://test.pypi.org](test repository):
+
+```
+python3 -m twine upload --repository testpypi release/*
+```
+
+The package can then be installed using
+
+```
+pip3 install -i https://test.pypi.org/simple/ tink
+```
+
+In order to upload it to the PyPI repository run
+
+```
+python3 -m twine upload release/*
+```
+
diff --git a/python/tools/distribution/build_linux_binary_wheels.sh b/python/tools/distribution/build_linux_binary_wheels.sh
new file mode 100755
index 0000000..997abf9
--- /dev/null
+++ b/python/tools/distribution/build_linux_binary_wheels.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+# This script builds binary wheels of Tink for Linux based on PEP 599. It
+# should be run inside a manylinux2014 Docker container to have the correct
+# environment setup.
+
+set -euo pipefail
+
+# Get dependencies which are needed for building Tink
+# Install Bazel which is needed for building C++ extensions
+BAZEL_VERSION='3.1.0'
+curl -OL https://github.com/bazelbuild/bazel/releases/download/${BAZEL_VERSION}/bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh
+chmod +x bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh
+./bazel-${BAZEL_VERSION}-installer-linux-x86_64.sh
+
+# Install Protoc which is needed for compiling the protos
+PROTOC_ZIP='protoc-3.11.4-linux-x86_64.zip'
+curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.11.4/${PROTOC_ZIP}
+unzip -o "${PROTOC_ZIP}" -d /usr/local bin/protoc
+
+# Setup required for Tink
+export TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH=/tmp/tink
+
+# Build wheel for Python 3.7
+(
+  # Set Path to Python3.7
+  export PATH=$PATH:/opt/python/cp37-cp37m/bin
+
+  # Create binary wheel
+  pip wheel .
+)
+
+# This is needed to ensure we get a clean build, as otherwise parts of the
+# Python 3.8 package use compiled code for Python 3.7.
+bazel clean --expunge
+
+# Build wheel for Python 3.8
+(
+  # Set Path to Python3.8
+  export PATH=$PATH:/opt/python/cp38-cp38/bin
+
+  # Create binary wheel
+  pip wheel .
+)
+
+# Repair wheels to convert them from linux to manylinux.
+for wheel in ./tink*.whl; do
+    auditwheel repair "$wheel" -w release
+done
diff --git a/python/tools/distribution/create_release.sh b/python/tools/distribution/create_release.sh
new file mode 100755
index 0000000..df7ab72
--- /dev/null
+++ b/python/tools/distribution/create_release.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+# This script creates a release of Tink Python which includes a source
+# distribution and binary wheels for Linux. The release script automatically
+# all Python tests on each binary wheel and the source distribution.
+set -euo pipefail
+
+mkdir -p release
+
+TINK_BASE=${PWD}/..
+
+# Use signatures for getting images from registry (see https://docs.docker.com/engine/security/trust/content_trust/)
+export DOCKER_CONTENT_TRUST=1
+
+# Build binary wheels
+docker run --volume $TINK_BASE:/tmp/tink --workdir /tmp/tink/python quay.io/pypa/manylinux2014_x86_64@sha256:cf8940dd5ce452d7741c592e229c28e802cbce5b3074d88a299e4f67f55efba4 /tmp/tink/python/tools/distribution/build_linux_binary_wheels.sh
+
+# Test binary wheels
+docker run --volume $TINK_BASE:/tmp/tink --workdir /tmp/tink/python quay.io/pypa/manylinux2014_x86_64@sha256:cf8940dd5ce452d7741c592e229c28e802cbce5b3074d88a299e4f67f55efba4 /tmp/tink/python/tools/distribution/test_linux_binary_wheels.sh
+
+# Build source wheels
+pip3 install wheel
+export TINK_PYTHON_SETUPTOOLS_OVERRIDE_BASE_PATH=$TINK_BASE
+sudo python3 setup.py sdist
+cp dist/*.tar.gz release/
+
+# Test install from source wheel
+pip3 install release/*.tar.gz
+find tink/ -not -path "*cc/pybind*" -type f -name "*_test.py" -print0 | xargs -0 -n1 python3
diff --git a/python/tools/distribution/test_linux_binary_wheels.sh b/python/tools/distribution/test_linux_binary_wheels.sh
new file mode 100755
index 0000000..b9befd2
--- /dev/null
+++ b/python/tools/distribution/test_linux_binary_wheels.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+set -euo pipefail
+
+export TINK_SRC_PATH=/tmp/tink
+
+# This link is required on CentOS, as curl used in the AWS SDK looks for the
+# certificates in this location. Removing this line will cause the AWS KMS tests
+# to fail.
+ln -s /etc/ssl/certs/ca-bundle.trust.crt /etc/ssl/certs/ca-certificates.crt
+
+# Test wheel for Python 3.7
+(
+  PATH=$PATH:/opt/python/cp37-cp37m/bin
+  pip3 install release/*-cp37-cp37m-manylinux2014_x86_64.whl
+  find tink/ -not -path "*cc/pybind*" -type f -name "*_test.py" -print0 | xargs -0 -n1 python3
+)
+
+# Test wheel for Python 3.8
+(
+  PATH=$PATH:/opt/python/cp38-cp38/bin
+  pip3 install release/*-cp38-cp38-manylinux2014_x86_64.whl
+  find tink/ -not -path "*cc/pybind*" -type f -name "*_test.py" -print0 | xargs -0 -n1 python3
+)
diff --git a/testing/cc/.bazelversion b/testing/cc/.bazelversion
new file mode 100644
index 0000000..fd2a018
--- /dev/null
+++ b/testing/cc/.bazelversion
@@ -0,0 +1 @@
+3.1.0
diff --git a/testing/cc/BUILD.bazel b/testing/cc/BUILD.bazel
new file mode 100644
index 0000000..caed1b1
--- /dev/null
+++ b/testing/cc/BUILD.bazel
@@ -0,0 +1,246 @@
+load("@rules_proto_grpc//cpp:defs.bzl", "cpp_grpc_library")
+
+package(
+    default_testonly = 1,
+    default_visibility = ["//visibility:public"],
+)
+
+licenses(["notice"])
+
+cpp_grpc_library(
+    name = "testing_api_cpp_library",
+    service_namespace = "testing_api",
+    deps = ["@tink_base//proto/testing:testing_api_proto"],
+)
+
+cc_library(
+    name = "metadata_impl",
+    srcs = ["metadata_impl.cc"],
+    hdrs = ["metadata_impl.h"],
+    deps = [
+        ":testing_api_cpp_library",
+        "@com_google_absl//absl/base:core_headers",
+        "@tink_cc",
+    ],
+)
+
+cc_test(
+    name = "metadata_impl_test",
+    srcs = ["metadata_impl_test.cc"],
+    deps = [
+        ":metadata_impl",
+        ":testing_api_cpp_library",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_library(
+    name = "keyset_impl",
+    srcs = ["keyset_impl.cc"],
+    hdrs = ["keyset_impl.h"],
+    deps = [
+        ":testing_api_cpp_library",
+        "@com_google_absl//absl/base:core_headers",
+        "@tink_cc//:binary_keyset_reader",
+        "@tink_cc//:binary_keyset_writer",
+        "@tink_cc//:cleartext_keyset_handle",
+        "@tink_cc//:json_keyset_reader",
+        "@tink_cc//:json_keyset_writer",
+        "@tink_cc//:keyset_handle",
+    ],
+)
+
+cc_test(
+    name = "keyset_impl_test",
+    srcs = ["keyset_impl_test.cc"],
+    deps = [
+        ":keyset_impl",
+        ":testing_api_cpp_library",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc//:binary_keyset_reader",
+        "@tink_cc//:binary_keyset_writer",
+        "@tink_cc//:json_keyset_reader",
+        "@tink_cc//:json_keyset_writer",
+        "@tink_cc//aead:aead_key_templates",
+        "@tink_cc//config:tink_config",
+        "@tink_cc//hybrid:hybrid_key_templates",
+    ],
+)
+
+cc_library(
+    name = "aead_impl",
+    srcs = ["aead_impl.cc"],
+    hdrs = ["aead_impl.h"],
+    deps = [
+        ":testing_api_cpp_library",
+        "@com_google_absl//absl/base:core_headers",
+        "@tink_cc//:binary_keyset_reader",
+        "@tink_cc//:cleartext_keyset_handle",
+    ],
+)
+
+cc_test(
+    name = "aead_impl_test",
+    srcs = ["aead_impl_test.cc"],
+    deps = [
+        ":aead_impl",
+        ":testing_api_cpp_library",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc//:binary_keyset_writer",
+        "@tink_cc//aead:aead_config",
+        "@tink_cc//aead:aead_key_templates",
+    ],
+)
+
+cc_library(
+    name = "deterministic_aead_impl",
+    srcs = ["deterministic_aead_impl.cc"],
+    hdrs = ["deterministic_aead_impl.h"],
+    deps = [
+        ":testing_api_cpp_library",
+        "@com_google_absl//absl/base:core_headers",
+        "@tink_cc",
+        "@tink_cc//:binary_keyset_reader",
+        "@tink_cc//:cleartext_keyset_handle",
+    ],
+)
+
+cc_test(
+    name = "deterministic_aead_impl_test",
+    srcs = ["deterministic_aead_impl_test.cc"],
+    deps = [
+        ":deterministic_aead_impl",
+        ":testing_api_cpp_library",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc//:binary_keyset_writer",
+        "@tink_cc//daead:deterministic_aead_config",
+        "@tink_cc//daead:deterministic_aead_key_templates",
+    ],
+)
+
+cc_library(
+    name = "streaming_aead_impl",
+    srcs = ["streaming_aead_impl.cc"],
+    hdrs = ["streaming_aead_impl.h"],
+    deps = [
+        ":testing_api_cpp_library",
+        "@com_google_absl//absl/base:core_headers",
+        "@tink_cc",
+        "@tink_cc//:binary_keyset_reader",
+        "@tink_cc//:cleartext_keyset_handle",
+        "@tink_cc//util:istream_input_stream",
+        "@tink_cc//util:ostream_output_stream",
+        "@tink_cc//util:status",
+    ],
+)
+
+cc_test(
+    name = "streaming_aead_impl_test",
+    srcs = ["streaming_aead_impl_test.cc"],
+    deps = [
+        ":streaming_aead_impl",
+        ":testing_api_cpp_library",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc//:binary_keyset_writer",
+        "@tink_cc//streamingaead:streaming_aead_config",
+        "@tink_cc//streamingaead:streaming_aead_key_templates",
+    ],
+)
+
+cc_library(
+    name = "mac_impl",
+    srcs = ["mac_impl.cc"],
+    hdrs = ["mac_impl.h"],
+    deps = [
+        ":testing_api_cpp_library",
+        "@com_google_absl//absl/base:core_headers",
+        "@tink_cc",
+        "@tink_cc//:binary_keyset_reader",
+        "@tink_cc//:cleartext_keyset_handle",
+    ],
+)
+
+cc_test(
+    name = "mac_impl_test",
+    srcs = ["mac_impl_test.cc"],
+    deps = [
+        ":mac_impl",
+        ":testing_api_cpp_library",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc//:binary_keyset_writer",
+        "@tink_cc//mac:mac_config",
+        "@tink_cc//mac:mac_key_templates",
+    ],
+)
+
+cc_library(
+    name = "hybrid_impl",
+    srcs = ["hybrid_impl.cc"],
+    hdrs = ["hybrid_impl.h"],
+    deps = [
+        ":testing_api_cpp_library",
+        "@com_google_absl//absl/base:core_headers",
+        "@tink_cc",
+        "@tink_cc//:binary_keyset_reader",
+        "@tink_cc//:cleartext_keyset_handle",
+    ],
+)
+
+cc_test(
+    name = "hybrid_impl_test",
+    srcs = ["hybrid_impl_test.cc"],
+    deps = [
+        ":hybrid_impl",
+        ":testing_api_cpp_library",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc//:binary_keyset_writer",
+        "@tink_cc//hybrid:hybrid_config",
+        "@tink_cc//hybrid:hybrid_key_templates",
+    ],
+)
+
+cc_library(
+    name = "signature_impl",
+    srcs = ["signature_impl.cc"],
+    hdrs = ["signature_impl.h"],
+    deps = [
+        ":testing_api_cpp_library",
+        "@com_google_absl//absl/base:core_headers",
+        "@tink_cc",
+        "@tink_cc//:binary_keyset_reader",
+        "@tink_cc//:cleartext_keyset_handle",
+    ],
+)
+
+cc_test(
+    name = "signature_impl_test",
+    srcs = ["signature_impl_test.cc"],
+    deps = [
+        ":signature_impl",
+        ":testing_api_cpp_library",
+        "@com_google_googletest//:gtest_main",
+        "@tink_cc//:binary_keyset_writer",
+        "@tink_cc//signature:signature_config",
+        "@tink_cc//signature:signature_key_templates",
+    ],
+)
+
+cc_binary(
+    name = "testing_server",
+    srcs = ["testing_server.cc"],
+    deps = [
+        ":aead_impl",
+        ":deterministic_aead_impl",
+        ":hybrid_impl",
+        ":keyset_impl",
+        ":mac_impl",
+        ":metadata_impl",
+        ":signature_impl",
+        ":streaming_aead_impl",
+        ":testing_api_cpp_library",
+        "@com_google_absl//absl/base:core_headers",
+        "@com_google_absl//absl/flags:flag",
+        "@com_google_absl//absl/flags:parse",
+        "@tink_cc//config:tink_config",
+    ],
+)
diff --git a/testing/cc/WORKSPACE b/testing/cc/WORKSPACE
new file mode 100644
index 0000000..c682047
--- /dev/null
+++ b/testing/cc/WORKSPACE
@@ -0,0 +1,42 @@
+workspace(name = "testing_cc")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+local_repository(
+    name = "tink_base",
+    path = "../..",
+)
+
+local_repository(
+    name = "tink_cc",
+    path = "../../cc",
+)
+
+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()
+
+load("@tink_cc//:tink_cc_deps.bzl", "tink_cc_deps")
+tink_cc_deps()
+
+load("@tink_cc//:tink_cc_deps_init.bzl", "tink_cc_deps_init")
+tink_cc_deps_init()
+
+http_archive(
+    name = "rules_proto_grpc",
+    urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/1.0.2.tar.gz"],
+    sha256 = "5f0f2fc0199810c65a2de148a52ba0aff14d631d4e8202f41aff6a9d590a471b",
+    strip_prefix = "rules_proto_grpc-1.0.2",
+)
+
+load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_toolchains", "rules_proto_grpc_repos")
+rules_proto_grpc_toolchains()
+rules_proto_grpc_repos()
+
+load("@rules_proto_grpc//cpp:repositories.bzl", rules_proto_grpc_cpp_repos="cpp_repos")
+rules_proto_grpc_cpp_repos()
+
+load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
+grpc_deps()
diff --git a/testing/cc/aead_impl.cc b/testing/cc/aead_impl.cc
new file mode 100644
index 0000000..16f35ed
--- /dev/null
+++ b/testing/cc/aead_impl.cc
@@ -0,0 +1,94 @@
+// 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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Implementation of an AEAD Service.
+#include "aead_impl.h"
+
+#include "tink/aead.h"
+#include "tink/binary_keyset_reader.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+using ::crypto::tink::BinaryKeysetReader;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::grpc::ServerContext;
+using ::grpc::Status;
+
+AeadImpl::AeadImpl() {}
+
+// Encrypts a message
+::grpc::Status AeadImpl::Encrypt(grpc::ServerContext* context,
+                                 const AeadEncryptRequest* request,
+                                 AeadEncryptResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto aead_result =
+      handle_result.ValueOrDie()->GetPrimitive<crypto::tink::Aead>();
+  if (!aead_result.ok()) {
+    response->set_err(aead_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto encrypt_result = aead_result.ValueOrDie()->Encrypt(
+      request->plaintext(), request->associated_data());
+  if (!encrypt_result.ok()) {
+    response->set_err(encrypt_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_ciphertext(encrypt_result.ValueOrDie());
+  return ::grpc::Status::OK;
+}
+
+// Decrypts a ciphertext
+::grpc::Status AeadImpl::Decrypt(grpc::ServerContext* context,
+                                 const AeadDecryptRequest* request,
+                                 AeadDecryptResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto aead_result =
+      handle_result.ValueOrDie()->GetPrimitive<crypto::tink::Aead>();
+  if (!aead_result.ok()) {
+    response->set_err(aead_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto decrypt_result = aead_result.ValueOrDie()->Decrypt(
+      request->ciphertext(), request->associated_data());
+  if (!decrypt_result.ok()) {
+    response->set_err(decrypt_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_plaintext(decrypt_result.ValueOrDie());
+  return ::grpc::Status::OK;
+}
+
+}  // namespace tink_testing_api
diff --git a/testing/cc/aead_impl.h b/testing/cc/aead_impl.h
new file mode 100644
index 0000000..75bc274
--- /dev/null
+++ b/testing/cc/aead_impl.h
@@ -0,0 +1,44 @@
+// 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_TESTING_SERIVCES_AEAD_IMPL_H_
+#define TINK_TESTING_SERIVCES_AEAD_IMPL_H_
+
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/server_context.h>
+#include <grpcpp/support/status.h>
+
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+// An Aead Service.
+class AeadImpl final : public Aead::Service {
+ public:
+  AeadImpl();
+
+  ~AeadImpl() override = default;
+
+  grpc::Status Encrypt(grpc::ServerContext* context,
+                       const AeadEncryptRequest* request,
+                       AeadEncryptResponse* response) override;
+
+  grpc::Status Decrypt(grpc::ServerContext* context,
+                       const AeadDecryptRequest* request,
+                       AeadDecryptResponse* response) override;
+};
+
+}  // namespace tink_testing_api
+
+#endif  // TINK_TESTING_SERIVCES_AEAD_IMPL_H_
diff --git a/testing/cc/aead_impl_test.cc b/testing/cc/aead_impl_test.cc
new file mode 100644
index 0000000..c5b0e60
--- /dev/null
+++ b/testing/cc/aead_impl_test.cc
@@ -0,0 +1,113 @@
+// 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 "aead_impl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/aead/aead_config.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/binary_keyset_writer.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::AeadKeyTemplates;
+using ::crypto::tink::BinaryKeysetWriter;
+using ::crypto::tink::CleartextKeysetHandle;
+
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::tink_testing_api::AeadDecryptRequest;
+using ::tink_testing_api::AeadEncryptRequest;
+using ::tink_testing_api::AeadEncryptResponse;
+using ::tink_testing_api::AeadDecryptResponse;
+
+using crypto::tink::KeysetHandle;
+using google::crypto::tink::KeyTemplate;
+
+std::string ValidKeyset() {
+  const KeyTemplate& key_template = AeadKeyTemplates::Aes128Eax();
+  auto handle_result = KeysetHandle::GenerateNew(key_template);
+  EXPECT_TRUE(handle_result.ok());
+  std::stringbuf keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&keyset));
+  EXPECT_TRUE(writer_result.ok());
+
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             *handle_result.ValueOrDie());
+  EXPECT_TRUE(status.ok());
+  return keyset.str();
+}
+
+class AeadImplTest : public ::testing::Test {
+ protected:
+  static void SetUpTestSuite() { ASSERT_TRUE(AeadConfig::Register().ok()); }
+};
+
+TEST_F(AeadImplTest, EncryptDecryptSuccess) {
+  tink_testing_api::AeadImpl aead;
+  std::string keyset = ValidKeyset();
+  AeadEncryptRequest enc_request;
+  enc_request.set_keyset(keyset);
+  enc_request.set_plaintext("Plain text");
+  enc_request.set_associated_data("ad");
+  AeadEncryptResponse enc_response;
+
+  EXPECT_TRUE(aead.Encrypt(nullptr, &enc_request, &enc_response).ok());
+  EXPECT_THAT(enc_response.err(), IsEmpty());
+
+  AeadDecryptRequest dec_request;
+  dec_request.set_keyset(keyset);
+  dec_request.set_ciphertext(enc_response.ciphertext());
+  dec_request.set_associated_data("ad");
+  AeadDecryptResponse dec_response;
+
+  EXPECT_TRUE(aead.Decrypt(nullptr, &dec_request, &dec_response).ok());
+  EXPECT_THAT(dec_response.err(), IsEmpty());
+  EXPECT_THAT(dec_response.plaintext(), Eq("Plain text"));
+}
+
+TEST_F(AeadImplTest, EncryptBadKeysetFail) {
+  tink_testing_api::AeadImpl aead;
+  AeadEncryptRequest enc_request;
+  enc_request.set_keyset("bad keyset");
+  enc_request.set_plaintext("Plain text");
+  enc_request.set_associated_data("ad");
+  AeadEncryptResponse enc_response;
+
+  EXPECT_TRUE(aead.Encrypt(nullptr, &enc_request, &enc_response).ok());
+  EXPECT_THAT(enc_response.err(), Not(IsEmpty()));
+}
+
+TEST_F(AeadImplTest, DecryptBadCiphertextFail) {
+  tink_testing_api::AeadImpl aead;
+  std::string keyset = ValidKeyset();
+  AeadDecryptRequest dec_request;
+  dec_request.set_keyset(keyset);
+  dec_request.set_ciphertext("bad ciphertext");
+  dec_request.set_associated_data("ad");
+  AeadDecryptResponse dec_response;
+
+  EXPECT_TRUE(aead.Decrypt(nullptr, &dec_request, &dec_response).ok());
+  EXPECT_THAT(dec_response.err(), Not(IsEmpty()));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/testing/cc/deterministic_aead_impl.cc b/testing/cc/deterministic_aead_impl.cc
new file mode 100644
index 0000000..a11d9e0
--- /dev/null
+++ b/testing/cc/deterministic_aead_impl.cc
@@ -0,0 +1,96 @@
+// 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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Implementation of an Deterministic AEAD Service.
+#include "deterministic_aead_impl.h"
+
+#include "tink/binary_keyset_reader.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/deterministic_aead.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+using ::crypto::tink::BinaryKeysetReader;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::grpc::ServerContext;
+using ::grpc::Status;
+
+DeterministicAeadImpl::DeterministicAeadImpl() {}
+
+// Encrypts a message
+::grpc::Status DeterministicAeadImpl::EncryptDeterministically(
+    grpc::ServerContext* context,
+    const DeterministicAeadEncryptRequest* request,
+    DeterministicAeadEncryptResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto daead_result = handle_result.ValueOrDie()
+                          ->GetPrimitive<crypto::tink::DeterministicAead>();
+  if (!daead_result.ok()) {
+    response->set_err(daead_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto encrypt_result = daead_result.ValueOrDie()->EncryptDeterministically(
+      request->plaintext(), request->associated_data());
+  if (!encrypt_result.ok()) {
+    response->set_err(encrypt_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_ciphertext(encrypt_result.ValueOrDie());
+  return ::grpc::Status::OK;
+}
+
+// Decrypts a ciphertext
+::grpc::Status DeterministicAeadImpl::DecryptDeterministically(
+    grpc::ServerContext* context,
+    const DeterministicAeadDecryptRequest* request,
+    DeterministicAeadDecryptResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto daead_result = handle_result.ValueOrDie()
+                          ->GetPrimitive<crypto::tink::DeterministicAead>();
+  if (!daead_result.ok()) {
+    response->set_err(daead_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto decrypt_result = daead_result.ValueOrDie()->DecryptDeterministically(
+      request->ciphertext(), request->associated_data());
+  if (!decrypt_result.ok()) {
+    response->set_err(decrypt_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_plaintext(decrypt_result.ValueOrDie());
+  return ::grpc::Status::OK;
+}
+
+}  // namespace tink_testing_api
diff --git a/testing/cc/deterministic_aead_impl.h b/testing/cc/deterministic_aead_impl.h
new file mode 100644
index 0000000..ce23375
--- /dev/null
+++ b/testing/cc/deterministic_aead_impl.h
@@ -0,0 +1,46 @@
+// 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_TESTING_SERIVCES_DETERMINISTIC_AEAD_IMPL_H_
+#define TINK_TESTING_SERIVCES_DETERMINISTIC_AEAD_IMPL_H_
+
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/server_context.h>
+#include <grpcpp/support/status.h>
+
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+// An DeterministicAead Service.
+class DeterministicAeadImpl final : public DeterministicAead::Service {
+ public:
+  DeterministicAeadImpl();
+
+  ~DeterministicAeadImpl() override = default;
+
+  grpc::Status EncryptDeterministically(
+      grpc::ServerContext* context,
+      const DeterministicAeadEncryptRequest* request,
+      DeterministicAeadEncryptResponse* response) override;
+
+  grpc::Status DecryptDeterministically(
+      grpc::ServerContext* context,
+      const DeterministicAeadDecryptRequest* request,
+      DeterministicAeadDecryptResponse* response) override;
+};
+
+}  // namespace tink_testing_api
+
+#endif  // TINK_TESTING_SERIVCES_DETERMINISTIC_AEAD_IMPL_H_
diff --git a/testing/cc/deterministic_aead_impl_test.cc b/testing/cc/deterministic_aead_impl_test.cc
new file mode 100644
index 0000000..af57073
--- /dev/null
+++ b/testing/cc/deterministic_aead_impl_test.cc
@@ -0,0 +1,123 @@
+// 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 "deterministic_aead_impl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/binary_keyset_writer.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/daead/deterministic_aead_config.h"
+#include "tink/daead/deterministic_aead_key_templates.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::BinaryKeysetWriter;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::crypto::tink::DeterministicAeadKeyTemplates;
+
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::tink_testing_api::DeterministicAeadDecryptRequest;
+using ::tink_testing_api::DeterministicAeadDecryptResponse;
+using ::tink_testing_api::DeterministicAeadEncryptRequest;
+using ::tink_testing_api::DeterministicAeadEncryptResponse;
+
+using crypto::tink::KeysetHandle;
+using google::crypto::tink::KeyTemplate;
+
+std::string ValidKeyset() {
+  const KeyTemplate& key_template = DeterministicAeadKeyTemplates::Aes256Siv();
+  auto handle_result = KeysetHandle::GenerateNew(key_template);
+  EXPECT_TRUE(handle_result.ok());
+  std::stringbuf keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&keyset));
+  EXPECT_TRUE(writer_result.ok());
+
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             *handle_result.ValueOrDie());
+  EXPECT_TRUE(status.ok());
+  return keyset.str();
+}
+
+class DeterministicAeadImplTest : public ::testing::Test {
+ protected:
+  static void SetUpTestSuite() {
+    ASSERT_TRUE(DeterministicAeadConfig::Register().ok());
+  }
+};
+
+TEST_F(DeterministicAeadImplTest, EncryptDecryptSuccess) {
+  tink_testing_api::DeterministicAeadImpl daead;
+  std::string keyset = ValidKeyset();
+  DeterministicAeadEncryptRequest enc_request;
+  enc_request.set_keyset(keyset);
+  enc_request.set_plaintext("Plain text");
+  enc_request.set_associated_data("ad");
+  DeterministicAeadEncryptResponse enc_response;
+
+  EXPECT_TRUE(
+      daead.EncryptDeterministically(nullptr, &enc_request, &enc_response)
+          .ok());
+  EXPECT_THAT(enc_response.err(), IsEmpty());
+
+  DeterministicAeadDecryptRequest dec_request;
+  dec_request.set_keyset(keyset);
+  dec_request.set_ciphertext(enc_response.ciphertext());
+  dec_request.set_associated_data("ad");
+  DeterministicAeadDecryptResponse dec_response;
+
+  EXPECT_TRUE(
+      daead.DecryptDeterministically(nullptr, &dec_request, &dec_response)
+          .ok());
+  EXPECT_THAT(dec_response.err(), IsEmpty());
+  EXPECT_THAT(dec_response.plaintext(), Eq("Plain text"));
+}
+
+TEST_F(DeterministicAeadImplTest, EncryptBadKeysetFail) {
+  tink_testing_api::DeterministicAeadImpl daead;
+  DeterministicAeadEncryptRequest enc_request;
+  enc_request.set_keyset("bad keyset");
+  enc_request.set_plaintext("Plain text");
+  enc_request.set_associated_data("ad");
+  DeterministicAeadEncryptResponse enc_response;
+
+  EXPECT_TRUE(
+      daead.EncryptDeterministically(nullptr, &enc_request, &enc_response)
+          .ok());
+  EXPECT_THAT(enc_response.err(), Not(IsEmpty()));
+}
+
+TEST_F(DeterministicAeadImplTest, DecryptBadCiphertextFail) {
+  tink_testing_api::DeterministicAeadImpl daead;
+  std::string keyset = ValidKeyset();
+  DeterministicAeadDecryptRequest dec_request;
+  dec_request.set_keyset(keyset);
+  dec_request.set_ciphertext("bad ciphertext");
+  dec_request.set_associated_data("ad");
+  DeterministicAeadDecryptResponse dec_response;
+
+  EXPECT_TRUE(
+      daead.DecryptDeterministically(nullptr, &dec_request, &dec_response)
+          .ok());
+  EXPECT_THAT(dec_response.err(), Not(IsEmpty()));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/testing/cc/hybrid_impl.cc b/testing/cc/hybrid_impl.cc
new file mode 100644
index 0000000..e20cf90
--- /dev/null
+++ b/testing/cc/hybrid_impl.cc
@@ -0,0 +1,97 @@
+// 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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Implementation of a Hybrid encryption service
+#include "hybrid_impl.h"
+
+#include "tink/binary_keyset_reader.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/hybrid_decrypt.h"
+#include "tink/hybrid_encrypt.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+using ::crypto::tink::BinaryKeysetReader;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::grpc::ServerContext;
+using ::grpc::Status;
+
+HybridImpl::HybridImpl() {}
+
+// Encrypts a message
+::grpc::Status HybridImpl::Encrypt(grpc::ServerContext* context,
+                                   const HybridEncryptRequest* request,
+                                   HybridEncryptResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->public_keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto public_handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!public_handle_result.ok()) {
+    response->set_err(public_handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto hybrid_encrypt_result =
+      public_handle_result.ValueOrDie()
+          ->GetPrimitive<crypto::tink::HybridEncrypt>();
+  if (!hybrid_encrypt_result.ok()) {
+    response->set_err(hybrid_encrypt_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto enc_result = hybrid_encrypt_result.ValueOrDie()->Encrypt(
+      request->plaintext(), request->context_info());
+  if (!enc_result.ok()) {
+    response->set_err(enc_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_ciphertext(enc_result.ValueOrDie());
+  return ::grpc::Status::OK;
+}
+
+// Decrypts a ciphertext
+::grpc::Status HybridImpl::Decrypt(grpc::ServerContext* context,
+                                   const HybridDecryptRequest* request,
+                                   HybridDecryptResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->private_keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto private_handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!private_handle_result.ok()) {
+    response->set_err(private_handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto hybrid_decrypt_result =
+      private_handle_result.ValueOrDie()
+          ->GetPrimitive<crypto::tink::HybridDecrypt>();
+  if (!hybrid_decrypt_result.ok()) {
+    response->set_err(hybrid_decrypt_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto dec_result = hybrid_decrypt_result.ValueOrDie()->Decrypt(
+      request->ciphertext(), request->context_info());
+  if (!dec_result.ok()) {
+    response->set_err(dec_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_plaintext(dec_result.ValueOrDie());
+  return ::grpc::Status::OK;
+}
+
+}  // namespace tink_testing_api
diff --git a/testing/cc/hybrid_impl.h b/testing/cc/hybrid_impl.h
new file mode 100644
index 0000000..d05f29c
--- /dev/null
+++ b/testing/cc/hybrid_impl.h
@@ -0,0 +1,44 @@
+// 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_TESTING_HYBRID_IMPL_H_
+#define TINK_TESTING_HYBRID_IMPL_H_
+
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/server_context.h>
+#include <grpcpp/support/status.h>
+
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+// A Hybrid encryption Service
+class HybridImpl final : public Hybrid::Service {
+ public:
+  HybridImpl();
+
+  ~HybridImpl() override = default;
+
+  grpc::Status Encrypt(grpc::ServerContext* context,
+                       const HybridEncryptRequest* request,
+                       HybridEncryptResponse* response) override;
+
+  grpc::Status Decrypt(grpc::ServerContext* context,
+                       const HybridDecryptRequest* request,
+                       HybridDecryptResponse* response) override;
+};
+
+}  // namespace tink_testing_api
+
+#endif  // TINK_TESTING_HYBRID_IMPL_H_
diff --git a/testing/cc/hybrid_impl_test.cc b/testing/cc/hybrid_impl_test.cc
new file mode 100644
index 0000000..1cf4aab
--- /dev/null
+++ b/testing/cc/hybrid_impl_test.cc
@@ -0,0 +1,123 @@
+// 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_impl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/binary_keyset_writer.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/hybrid/hybrid_config.h"
+#include "tink/hybrid/hybrid_key_templates.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::BinaryKeysetWriter;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::crypto::tink::HybridKeyTemplates;
+
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::tink_testing_api::HybridDecryptRequest;
+using ::tink_testing_api::HybridDecryptResponse;
+using ::tink_testing_api::HybridEncryptRequest;
+using ::tink_testing_api::HybridEncryptResponse;
+
+using crypto::tink::KeysetHandle;
+using google::crypto::tink::KeyTemplate;
+
+std::string KeysetBytes(const KeysetHandle& keyset_handle) {
+  std::stringbuf keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&keyset));
+  EXPECT_TRUE(writer_result.ok());
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             keyset_handle);
+  EXPECT_TRUE(status.ok());
+  return keyset.str();
+}
+
+class HybridImplTest : public ::testing::Test {
+ protected:
+  static void SetUpTestSuite() { ASSERT_TRUE(HybridConfig::Register().ok()); }
+};
+
+TEST_F(HybridImplTest, EncryptDecryptSuccess) {
+  tink_testing_api::HybridImpl hybrid;
+  const KeyTemplate& key_template =
+      HybridKeyTemplates::EciesP256HkdfHmacSha256Aes128Gcm();
+  auto private_handle_result = KeysetHandle::GenerateNew(key_template);
+  EXPECT_TRUE(private_handle_result.ok());
+  auto public_handle_result =
+      private_handle_result.ValueOrDie()->GetPublicKeysetHandle();
+  EXPECT_TRUE(public_handle_result.ok());
+
+  HybridEncryptRequest enc_request;
+  enc_request.set_public_keyset(
+      KeysetBytes(*public_handle_result.ValueOrDie()));
+  enc_request.set_plaintext("Plain text");
+  enc_request.set_context_info("context");
+  HybridEncryptResponse enc_response;
+
+  EXPECT_TRUE(hybrid.Encrypt(nullptr, &enc_request, &enc_response).ok());
+  EXPECT_THAT(enc_response.err(), IsEmpty());
+
+  HybridDecryptRequest dec_request;
+  dec_request.set_private_keyset(
+      KeysetBytes(*private_handle_result.ValueOrDie()));
+  dec_request.set_ciphertext(enc_response.ciphertext());
+  dec_request.set_context_info("context");
+  HybridDecryptResponse dec_response;
+
+  EXPECT_TRUE(hybrid.Decrypt(nullptr, &dec_request, &dec_response).ok());
+  EXPECT_THAT(dec_response.err(), IsEmpty());
+  EXPECT_THAT(dec_response.plaintext(), Eq("Plain text"));
+}
+
+TEST_F(HybridImplTest, EncryptBadKeysetFail) {
+  tink_testing_api::HybridImpl hybrid;
+  HybridEncryptRequest enc_request;
+  enc_request.set_public_keyset("bad keyset");
+  enc_request.set_plaintext("Plain text");
+  enc_request.set_context_info("context");
+  HybridEncryptResponse enc_response;
+
+  EXPECT_TRUE(hybrid.Encrypt(nullptr, &enc_request, &enc_response).ok());
+  EXPECT_THAT(enc_response.err(), Not(IsEmpty()));
+}
+
+TEST_F(HybridImplTest, DecryptBadCiphertextFail) {
+  tink_testing_api::HybridImpl hybrid;
+  const KeyTemplate& key_template =
+      HybridKeyTemplates::EciesP256HkdfHmacSha256Aes128Gcm();
+  auto private_handle_result = KeysetHandle::GenerateNew(key_template);
+  EXPECT_TRUE(private_handle_result.ok());
+
+  HybridDecryptRequest dec_request;
+  dec_request.set_private_keyset(
+      KeysetBytes(*private_handle_result.ValueOrDie()));
+  dec_request.set_ciphertext("bad ciphertext");
+  dec_request.set_context_info("context");
+  HybridDecryptResponse dec_response;
+
+  EXPECT_TRUE(hybrid.Decrypt(nullptr, &dec_request, &dec_response).ok());
+  EXPECT_THAT(dec_response.err(), Not(IsEmpty()));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/testing/cc/keyset_impl.cc b/testing/cc/keyset_impl.cc
new file mode 100644
index 0000000..0612f61
--- /dev/null
+++ b/testing/cc/keyset_impl.cc
@@ -0,0 +1,173 @@
+// 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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Implementation of a Keyset Service.
+#include "keyset_impl.h"
+
+#include "tink/binary_keyset_reader.h"
+#include "tink/binary_keyset_writer.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 "proto/tink.pb.h"
+
+namespace tink_testing_api {
+
+using ::crypto::tink::BinaryKeysetReader;
+using ::crypto::tink::BinaryKeysetWriter;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::crypto::tink::JsonKeysetReader;
+using ::crypto::tink::JsonKeysetWriter;
+using ::crypto::tink::KeysetHandle;
+using ::google::crypto::tink::KeyTemplate;
+using ::grpc::ServerContext;
+using ::grpc::Status;
+
+KeysetImpl::KeysetImpl() {}
+
+// Generates a new keyset with one key from a template.
+::grpc::Status KeysetImpl::Generate(grpc::ServerContext* context,
+                                    const KeysetGenerateRequest* request,
+                                    KeysetGenerateResponse* response) {
+  KeyTemplate key_template;
+  if (!key_template.ParseFromString(request->template_())) {
+    response->set_err("Could not parse the key template");
+    return ::grpc::Status::OK;
+  }
+  auto handle_result = KeysetHandle::GenerateNew(key_template);
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  std::stringbuf keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&keyset));
+  if (!writer_result.ok()) {
+    response->set_err(writer_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             *handle_result.ValueOrDie());
+  if (!status.ok()) {
+    response->set_err(status.error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_keyset(keyset.str());
+  return ::grpc::Status::OK;
+}
+
+// Returns a public keyset for a given private keyset.
+::grpc::Status KeysetImpl::Public(grpc::ServerContext* context,
+                                  const KeysetPublicRequest* request,
+                                  KeysetPublicResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->private_keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto private_handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!private_handle_result.ok()) {
+    response->set_err(private_handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto public_handle_result =
+      private_handle_result.ValueOrDie()->GetPublicKeysetHandle();
+  if (!public_handle_result.ok()) {
+    response->set_err(public_handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  std::stringbuf public_keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&public_keyset));
+  if (!writer_result.ok()) {
+    response->set_err(writer_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto status = CleartextKeysetHandle::Write(
+      writer_result.ValueOrDie().get(), *public_handle_result.ValueOrDie());
+  if (!status.ok()) {
+    response->set_err(status.error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_public_keyset(public_keyset.str());
+  return ::grpc::Status::OK;
+}
+
+// Converts a keyset from binary to JSON format.
+::grpc::Status KeysetImpl::ToJson(grpc::ServerContext* context,
+                                  const KeysetToJsonRequest* request,
+                                  KeysetToJsonResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  std::stringbuf json_keyset;
+  auto writer_result =
+      JsonKeysetWriter::New(absl::make_unique<std::ostream>(&json_keyset));
+  if (!writer_result.ok()) {
+    response->set_err(writer_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             *handle_result.ValueOrDie());
+  if (!status.ok()) {
+    response->set_err(status.error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_json_keyset(json_keyset.str());
+  return ::grpc::Status::OK;
+}
+
+// Converts a keyset from JSON to binary format.
+::grpc::Status KeysetImpl::FromJson(grpc::ServerContext* context,
+                                    const KeysetFromJsonRequest* request,
+                                    KeysetFromJsonResponse* response) {
+  auto reader_result = JsonKeysetReader::New(request->json_keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  std::stringbuf keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&keyset));
+  if (!writer_result.ok()) {
+    response->set_err(writer_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             *handle_result.ValueOrDie());
+  if (!status.ok()) {
+    response->set_err(status.error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_keyset(keyset.str());
+  return ::grpc::Status::OK;
+}
+
+}  // namespace tink_testing_api
diff --git a/testing/cc/keyset_impl.h b/testing/cc/keyset_impl.h
new file mode 100644
index 0000000..83674f4
--- /dev/null
+++ b/testing/cc/keyset_impl.h
@@ -0,0 +1,56 @@
+// 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_TESTING_SERIVCES_KEYSET_IMPL_H_
+#define TINK_TESTING_SERIVCES_KEYSET_IMPL_H_
+
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/server_context.h>
+#include <grpcpp/support/status.h>
+
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+// A Keyset Service.
+class KeysetImpl final : public Keyset::Service {
+ public:
+  KeysetImpl();
+
+  ~KeysetImpl() override = default;
+
+  // Generates a new keyset with one key from a template.
+  grpc::Status Generate(grpc::ServerContext* context,
+                        const KeysetGenerateRequest* request,
+                        KeysetGenerateResponse* response) override;
+
+  // Returns a public keyset for a given private keyset.
+  grpc::Status Public(grpc::ServerContext* context,
+                      const KeysetPublicRequest* request,
+                      KeysetPublicResponse* response) override;
+
+  // Converts a keyset from binary to JSON format.
+  grpc::Status ToJson(grpc::ServerContext* context,
+                      const KeysetToJsonRequest* request,
+                      KeysetToJsonResponse* response) override;
+
+  // Converts a keyset from JSON to binary format.
+  grpc::Status FromJson(grpc::ServerContext* context,
+                        const KeysetFromJsonRequest* request,
+                        KeysetFromJsonResponse* response) override;
+};
+
+}  // namespace tink_testing_api
+
+#endif  // TINK_TESTING_SERIVCES_KEYSET_IMPL_H_
diff --git a/testing/cc/keyset_impl_test.cc b/testing/cc/keyset_impl_test.cc
new file mode 100644
index 0000000..248adfe
--- /dev/null
+++ b/testing/cc/keyset_impl_test.cc
@@ -0,0 +1,198 @@
+// 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 "keyset_impl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/aead/aead_key_templates.h"
+#include "tink/binary_keyset_reader.h"
+#include "tink/binary_keyset_writer.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/config/tink_config.h"
+#include "tink/hybrid/hybrid_key_templates.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::AeadKeyTemplates;
+using ::crypto::tink::BinaryKeysetReader;
+using ::crypto::tink::BinaryKeysetWriter;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::crypto::tink::HybridKeyTemplates;
+using google::crypto::tink::KeyTemplate;
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using tink_testing_api::KeysetFromJsonRequest;
+using tink_testing_api::KeysetFromJsonResponse;
+using tink_testing_api::KeysetGenerateRequest;
+using tink_testing_api::KeysetGenerateResponse;
+using tink_testing_api::KeysetPublicRequest;
+using tink_testing_api::KeysetPublicResponse;
+using tink_testing_api::KeysetToJsonRequest;
+using tink_testing_api::KeysetToJsonResponse;
+
+class KeysetImplTest : public ::testing::Test {
+ protected:
+  static void SetUpTestSuite() { ASSERT_TRUE(TinkConfig::Register().ok()); }
+};
+
+TEST_F(KeysetImplTest, GenerateSuccess) {
+  tink_testing_api::KeysetImpl keyset;
+  const KeyTemplate& key_template = AeadKeyTemplates::Aes128Eax();
+  KeysetGenerateRequest request;
+  std::string templ;
+  EXPECT_TRUE(key_template.SerializeToString(&templ));
+  request.set_template_(templ);
+  KeysetGenerateResponse response;
+
+  EXPECT_TRUE(keyset.Generate(nullptr, &request, &response).ok());
+  EXPECT_THAT(response.err(), IsEmpty());
+
+  auto reader_result = BinaryKeysetReader::New(response.keyset());
+  ASSERT_TRUE(reader_result.ok());
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  EXPECT_TRUE(handle_result.ok());
+}
+
+TEST_F(KeysetImplTest, GenerateFail) {
+  tink_testing_api::KeysetImpl keyset;
+
+  KeysetGenerateRequest request;
+  request.set_template_("bad template");
+  KeysetGenerateResponse response;
+  EXPECT_TRUE(keyset.Generate(nullptr, &request, &response).ok());
+  EXPECT_THAT(response.err(), Not(IsEmpty()));
+}
+
+std::string ValidPrivateKeyset() {
+  auto handle_result = KeysetHandle::GenerateNew(
+      HybridKeyTemplates::EciesP256HkdfHmacSha256Aes128Gcm());
+  EXPECT_TRUE(handle_result.ok());
+  std::stringbuf keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&keyset));
+  EXPECT_TRUE(writer_result.ok());
+
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             *handle_result.ValueOrDie());
+  EXPECT_TRUE(status.ok());
+  return keyset.str();
+}
+
+TEST_F(KeysetImplTest, PublicSuccess) {
+  tink_testing_api::KeysetImpl keyset;
+
+  KeysetPublicRequest request;
+  request.set_private_keyset(ValidPrivateKeyset());
+  KeysetPublicResponse response;
+
+  EXPECT_TRUE(keyset.Public(nullptr, &request, &response).ok());
+  EXPECT_THAT(response.err(), IsEmpty());
+
+  auto reader_result = BinaryKeysetReader::New(response.public_keyset());
+  ASSERT_TRUE(reader_result.ok());
+  auto public_handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  EXPECT_TRUE(public_handle_result.ok());
+}
+
+TEST_F(KeysetImplTest, PublicFail) {
+  tink_testing_api::KeysetImpl keyset;
+
+  KeysetPublicRequest request;
+  request.set_private_keyset("bad keyset");
+  KeysetPublicResponse response;
+  EXPECT_TRUE(keyset.Public(nullptr, &request, &response).ok());
+  EXPECT_THAT(response.err(), Not(IsEmpty()));
+}
+
+TEST_F(KeysetImplTest, FromJsonSuccess) {
+  tink_testing_api::KeysetImpl keyset;
+  std::string json_keyset = R""""(
+        {
+          "primaryKeyId": 42,
+          "key": [
+            {
+              "keyData": {
+                "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+                "keyMaterialType": "SYMMETRIC",
+                "value": "AFakeTestKeyValue1234567"
+              },
+              "outputPrefixType": "TINK",
+              "keyId": 42,
+              "status": "ENABLED"
+            }
+          ]
+        })"""";
+  KeysetFromJsonRequest from_request;
+  from_request.set_json_keyset(json_keyset);
+  KeysetFromJsonResponse from_response;
+  EXPECT_TRUE(keyset.FromJson(nullptr, &from_request, &from_response).ok());
+  EXPECT_THAT(from_response.err(), IsEmpty());
+  std::string output = from_response.keyset();
+
+  auto reader_result = BinaryKeysetReader::New(from_response.keyset());
+  EXPECT_TRUE(reader_result.ok());
+  auto keyset_proto_result = reader_result.ValueOrDie()->Read();
+  EXPECT_TRUE(keyset_proto_result.ok());
+  EXPECT_THAT(keyset_proto_result.ValueOrDie()->primary_key_id(), Eq(42));
+}
+
+TEST_F(KeysetImplTest, ToFromJsonSuccess) {
+  tink_testing_api::KeysetImpl keyset;
+  std::string keyset_data = ValidPrivateKeyset();
+
+  KeysetToJsonRequest to_request;
+  to_request.set_keyset(keyset_data);
+  KeysetToJsonResponse to_response;
+  EXPECT_TRUE(keyset.ToJson(nullptr, &to_request, &to_response).ok());
+  EXPECT_THAT(to_response.err(), IsEmpty());
+  std::string json_keyset = to_response.json_keyset();
+
+  KeysetFromJsonRequest from_request;
+  from_request.set_json_keyset(json_keyset);
+  KeysetFromJsonResponse from_response;
+  EXPECT_TRUE(keyset.FromJson(nullptr, &from_request, &from_response).ok());
+  EXPECT_THAT(from_response.err(), IsEmpty());
+  std::string output = from_response.keyset();
+  EXPECT_THAT(from_response.keyset(), Eq(keyset_data));
+}
+
+TEST_F(KeysetImplTest, ToJsonFail) {
+  tink_testing_api::KeysetImpl keyset;
+
+  KeysetToJsonRequest request;
+  request.set_keyset("bad keyset");
+  KeysetToJsonResponse response;
+  EXPECT_TRUE(keyset.ToJson(nullptr, &request, &response).ok());
+  EXPECT_THAT(response.err(), Not(IsEmpty()));
+}
+
+TEST_F(KeysetImplTest, FromJsonFail) {
+  tink_testing_api::KeysetImpl keyset;
+
+  KeysetFromJsonRequest request;
+  request.set_json_keyset("bad json keyset");
+  KeysetFromJsonResponse response;
+  EXPECT_TRUE(keyset.FromJson(nullptr, &request, &response).ok());
+  EXPECT_THAT(response.err(), Not(IsEmpty()));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/testing/cc/mac_impl.cc b/testing/cc/mac_impl.cc
new file mode 100644
index 0000000..500263c
--- /dev/null
+++ b/testing/cc/mac_impl.cc
@@ -0,0 +1,92 @@
+// 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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Implementation of a MAC Service.
+#include "mac_impl.h"
+
+#include "tink/binary_keyset_reader.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/mac.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+using ::crypto::tink::BinaryKeysetReader;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::grpc::ServerContext;
+using ::grpc::Status;
+
+MacImpl::MacImpl() {}
+
+// Computes a MAC
+::grpc::Status MacImpl::ComputeMac(grpc::ServerContext* context,
+                                   const ComputeMacRequest* request,
+                                   ComputeMacResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto mac_result =
+      handle_result.ValueOrDie()->GetPrimitive<crypto::tink::Mac>();
+  if (!mac_result.ok()) {
+    response->set_err(mac_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto compute_result = mac_result.ValueOrDie()->ComputeMac(request->data());
+  if (!compute_result.ok()) {
+    response->set_err(compute_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_mac_value(compute_result.ValueOrDie());
+  return ::grpc::Status::OK;
+}
+
+// Verifies a MAC
+::grpc::Status MacImpl::VerifyMac(grpc::ServerContext* context,
+                                  const VerifyMacRequest* request,
+                                  VerifyMacResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto mac_result =
+      handle_result.ValueOrDie()->GetPrimitive<crypto::tink::Mac>();
+  if (!mac_result.ok()) {
+    response->set_err(mac_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto status =
+      mac_result.ValueOrDie()->VerifyMac(request->mac_value(), request->data());
+  if (!status.ok()) {
+    response->set_err(status.error_message());
+    return ::grpc::Status::OK;
+  }
+  return ::grpc::Status::OK;
+}
+
+}  // namespace tink_testing_api
diff --git a/testing/cc/mac_impl.h b/testing/cc/mac_impl.h
new file mode 100644
index 0000000..ebe9e7c
--- /dev/null
+++ b/testing/cc/mac_impl.h
@@ -0,0 +1,44 @@
+// 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_TESTING_MAC_IMPL_H_
+#define TINK_TESTING_MAC_IMPL_H_
+
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/server_context.h>
+#include <grpcpp/support/status.h>
+
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+// A MAC Service.
+class MacImpl final : public Mac::Service {
+ public:
+  MacImpl();
+
+  ~MacImpl() override = default;
+
+  grpc::Status ComputeMac(grpc::ServerContext* context,
+                          const ComputeMacRequest* request,
+                          ComputeMacResponse* response) override;
+
+  grpc::Status VerifyMac(grpc::ServerContext* context,
+                         const VerifyMacRequest* request,
+                         VerifyMacResponse* response) override;
+};
+
+}  // namespace tink_testing_api
+
+#endif  // TINK_TESTING_MAC_IMPL_H_
diff --git a/testing/cc/mac_impl_test.cc b/testing/cc/mac_impl_test.cc
new file mode 100644
index 0000000..480aa81
--- /dev/null
+++ b/testing/cc/mac_impl_test.cc
@@ -0,0 +1,109 @@
+// 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 "mac_impl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/binary_keyset_writer.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/mac/mac_config.h"
+#include "tink/mac/mac_key_templates.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::BinaryKeysetWriter;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::crypto::tink::MacKeyTemplates;
+
+using ::testing::IsEmpty;
+using ::tink_testing_api::ComputeMacRequest;
+using ::tink_testing_api::ComputeMacResponse;
+using ::tink_testing_api::VerifyMacRequest;
+using ::tink_testing_api::VerifyMacResponse;
+
+using crypto::tink::KeysetHandle;
+using google::crypto::tink::KeyTemplate;
+
+std::string ValidKeyset() {
+  const KeyTemplate& key_template = MacKeyTemplates::HmacSha256();
+  auto handle_result = KeysetHandle::GenerateNew(key_template);
+  EXPECT_TRUE(handle_result.ok());
+  std::stringbuf keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&keyset));
+  EXPECT_TRUE(writer_result.ok());
+
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             *handle_result.ValueOrDie());
+  EXPECT_TRUE(status.ok());
+  return keyset.str();
+}
+
+class MacImplTest : public ::testing::Test {
+ protected:
+  static void SetUpTestSuite() { ASSERT_TRUE(MacConfig::Register().ok()); }
+};
+
+TEST_F(MacImplTest, ComputeVerifySuccess) {
+  tink_testing_api::MacImpl mac;
+  std::string keyset = ValidKeyset();
+  ComputeMacRequest comp_request;
+  comp_request.set_keyset(keyset);
+  comp_request.set_data("some data");
+  ComputeMacResponse comp_response;
+
+  EXPECT_TRUE(mac.ComputeMac(nullptr, &comp_request, &comp_response).ok());
+  EXPECT_THAT(comp_response.err(), IsEmpty());
+
+  VerifyMacRequest verify_request;
+  verify_request.set_keyset(keyset);
+  verify_request.set_mac_value(comp_response.mac_value());
+  verify_request.set_data("some data");
+  VerifyMacResponse verify_response;
+
+  EXPECT_TRUE(mac.VerifyMac(nullptr, &verify_request, &verify_response).ok());
+  EXPECT_THAT(verify_response.err(), IsEmpty());
+}
+
+TEST_F(MacImplTest, ComputeBadKeysetFail) {
+  tink_testing_api::MacImpl mac;
+  ComputeMacRequest comp_request;
+  comp_request.set_keyset("bad keyset");
+  comp_request.set_data("some data");
+  ComputeMacResponse comp_response;
+
+  EXPECT_TRUE(mac.ComputeMac(nullptr, &comp_request, &comp_response).ok());
+  EXPECT_THAT(comp_response.err(), Not(IsEmpty()));
+}
+
+TEST_F(MacImplTest, VerifyBadCiphertextFail) {
+  tink_testing_api::MacImpl mac;
+  std::string keyset = ValidKeyset();
+  VerifyMacRequest verify_request;
+  verify_request.set_keyset(keyset);
+  verify_request.set_mac_value("bad mac value");
+  verify_request.set_data("some data");
+  VerifyMacResponse verify_response;
+
+  EXPECT_TRUE(mac.VerifyMac(nullptr, &verify_request, &verify_response).ok());
+  EXPECT_THAT(verify_response.err(), Not(IsEmpty()));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/testing/cc/metadata_impl.cc b/testing/cc/metadata_impl.cc
new file mode 100644
index 0000000..5d742a6
--- /dev/null
+++ b/testing/cc/metadata_impl.cc
@@ -0,0 +1,35 @@
+// 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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Implementation of a server metadata service.
+#include "metadata_impl.h"
+#include "tink/version.h"
+
+namespace tink_testing_api {
+
+using ::grpc::ServerContext;
+using ::grpc::Status;
+
+MetadataImpl::MetadataImpl() {}
+
+// Returns server info.
+grpc::Status MetadataImpl::GetServerInfo(grpc::ServerContext* context,
+                                         const ServerInfoRequest* request,
+                                         ServerInfoResponse* response) {
+  response->set_language("cc");
+  response->set_tink_version(crypto::tink::Version::kTinkVersion);
+  return ::grpc::Status::OK;
+}
+
+}  // namespace tink_testing_api
diff --git a/testing/cc/metadata_impl.h b/testing/cc/metadata_impl.h
new file mode 100644
index 0000000..1a2fa8c
--- /dev/null
+++ b/testing/cc/metadata_impl.h
@@ -0,0 +1,44 @@
+// 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_TESTING_METADATA_IMPL_H_
+#define TINK_TESTING_METADATA_IMPL_H_
+
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/server_context.h>
+#include <grpcpp/support/status.h>
+
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+// A Metadata Service.
+class MetadataImpl final : public Metadata::Service {
+ public:
+  MetadataImpl();
+
+  ~MetadataImpl() override = default;
+
+  // Returns server info.
+  grpc::Status GetServerInfo(grpc::ServerContext* context,
+                             const ServerInfoRequest* request,
+                             ServerInfoResponse* response) override;
+};
+
+}  // namespace tink_testing_api
+
+
+
+
+#endif  // TINK_TESTING_METADATA_IMPL_H_
diff --git a/testing/cc/metadata_impl_test.cc b/testing/cc/metadata_impl_test.cc
new file mode 100644
index 0000000..a5ab828
--- /dev/null
+++ b/testing/cc/metadata_impl_test.cc
@@ -0,0 +1,41 @@
+// 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 "metadata_impl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using tink_testing_api::ServerInfoRequest;
+using tink_testing_api::ServerInfoResponse;
+
+TEST(MetadataImplTest, GetServerInfo) {
+  tink_testing_api::MetadataImpl metadata;
+  ServerInfoRequest request;
+  ServerInfoResponse response;
+  EXPECT_TRUE(metadata.GetServerInfo(nullptr, &request, &response).ok());
+  EXPECT_THAT(response.language(), Eq("cc"));
+  EXPECT_THAT(response.tink_version(), Not(IsEmpty()));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/testing/cc/signature_impl.cc b/testing/cc/signature_impl.cc
new file mode 100644
index 0000000..0d1c44c
--- /dev/null
+++ b/testing/cc/signature_impl.cc
@@ -0,0 +1,93 @@
+// 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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Implementation of a Signature Service
+#include "signature_impl.h"
+
+#include "tink/binary_keyset_reader.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/public_key_sign.h"
+#include "tink/public_key_verify.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+using ::crypto::tink::BinaryKeysetReader;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::grpc::ServerContext;
+using ::grpc::Status;
+
+SignatureImpl::SignatureImpl() {}
+
+// Signs a message
+::grpc::Status SignatureImpl::Sign(grpc::ServerContext* context,
+                                   const SignatureSignRequest* request,
+                                   SignatureSignResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->private_keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto private_handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!private_handle_result.ok()) {
+    response->set_err(private_handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto signer_result = private_handle_result.ValueOrDie()
+                           ->GetPrimitive<crypto::tink::PublicKeySign>();
+  if (!signer_result.ok()) {
+    response->set_err(signer_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto sign_result = signer_result.ValueOrDie()->Sign(request->data());
+  if (!sign_result.ok()) {
+    response->set_err(sign_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  response->set_signature(sign_result.ValueOrDie());
+  return ::grpc::Status::OK;
+}
+
+// Verifies a signature
+::grpc::Status SignatureImpl::Verify(grpc::ServerContext* context,
+                                     const SignatureVerifyRequest* request,
+                                     SignatureVerifyResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->public_keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto public_handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!public_handle_result.ok()) {
+    response->set_err(public_handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto verifier_result = public_handle_result.ValueOrDie()
+                             ->GetPrimitive<crypto::tink::PublicKeyVerify>();
+  if (!verifier_result.ok()) {
+    response->set_err(verifier_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto status = verifier_result.ValueOrDie()->Verify(request->signature(),
+                                                     request->data());
+  if (!status.ok()) {
+    response->set_err(status.error_message());
+    return ::grpc::Status::OK;
+  }
+  return ::grpc::Status::OK;
+}
+
+}  // namespace tink_testing_api
diff --git a/testing/cc/signature_impl.h b/testing/cc/signature_impl.h
new file mode 100644
index 0000000..dd4d952
--- /dev/null
+++ b/testing/cc/signature_impl.h
@@ -0,0 +1,44 @@
+// 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_TESTING_SIGNATURE_IMPL_H_
+#define TINK_TESTING_SIGNATURE_IMPL_H_
+
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/server_context.h>
+#include <grpcpp/support/status.h>
+
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+// A Signature Service
+class SignatureImpl final : public Signature::Service {
+ public:
+  SignatureImpl();
+
+  ~SignatureImpl() override = default;
+
+  grpc::Status Sign(grpc::ServerContext* context,
+                    const SignatureSignRequest* request,
+                    SignatureSignResponse* response) override;
+
+  grpc::Status Verify(grpc::ServerContext* context,
+                      const SignatureVerifyRequest* request,
+                      SignatureVerifyResponse* response) override;
+};
+
+}  // namespace tink_testing_api
+
+#endif  // TINK_TESTING_SIGNATURE_IMPL_H_
diff --git a/testing/cc/signature_impl_test.cc b/testing/cc/signature_impl_test.cc
new file mode 100644
index 0000000..ce7cf83
--- /dev/null
+++ b/testing/cc/signature_impl_test.cc
@@ -0,0 +1,124 @@
+// 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 "signature_impl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/binary_keyset_writer.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/signature/signature_config.h"
+#include "tink/signature/signature_key_templates.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::BinaryKeysetWriter;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::crypto::tink::SignatureKeyTemplates;
+
+using ::testing::IsEmpty;
+using ::tink_testing_api::SignatureSignRequest;
+using ::tink_testing_api::SignatureSignResponse;
+using ::tink_testing_api::SignatureVerifyRequest;
+using ::tink_testing_api::SignatureVerifyResponse;
+
+using crypto::tink::KeysetHandle;
+using google::crypto::tink::KeyTemplate;
+
+std::string KeysetBytes(const KeysetHandle& keyset_handle) {
+  std::stringbuf keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&keyset));
+  EXPECT_TRUE(writer_result.ok());
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             keyset_handle);
+  EXPECT_TRUE(status.ok());
+  return keyset.str();
+}
+
+class SignatureImplTest : public ::testing::Test {
+ protected:
+  static void SetUpTestSuite() {
+    ASSERT_TRUE(SignatureConfig::Register().ok());
+  }
+};
+
+TEST_F(SignatureImplTest, SignVerifySuccess) {
+  tink_testing_api::SignatureImpl signature;
+  const KeyTemplate& key_template = SignatureKeyTemplates::EcdsaP256();
+  auto private_handle_result = KeysetHandle::GenerateNew(key_template);
+  EXPECT_TRUE(private_handle_result.ok());
+  auto public_handle_result =
+      private_handle_result.ValueOrDie()->GetPublicKeysetHandle();
+  EXPECT_TRUE(public_handle_result.ok());
+
+  SignatureSignRequest sign_request;
+  sign_request.set_private_keyset(
+      KeysetBytes(*private_handle_result.ValueOrDie()));
+  sign_request.set_data("some data");
+  SignatureSignResponse sign_response;
+
+  EXPECT_TRUE(signature.Sign(nullptr, &sign_request, &sign_response).ok());
+  EXPECT_THAT(sign_response.err(), IsEmpty());
+
+  SignatureVerifyRequest verify_request;
+  verify_request.set_public_keyset(
+      KeysetBytes(*public_handle_result.ValueOrDie()));
+  verify_request.set_signature(sign_response.signature());
+  verify_request.set_data("some data");
+  SignatureVerifyResponse verify_response;
+
+  EXPECT_TRUE(
+      signature.Verify(nullptr, &verify_request, &verify_response).ok());
+  EXPECT_THAT(verify_response.err(), IsEmpty());
+}
+
+TEST_F(SignatureImplTest, SignBadKeysetFail) {
+  tink_testing_api::SignatureImpl signature;
+  SignatureSignRequest sign_request;
+  sign_request.set_private_keyset("bad private keyset");
+  sign_request.set_data("some data");
+  SignatureSignResponse sign_response;
+
+  EXPECT_TRUE(signature.Sign(nullptr, &sign_request, &sign_response).ok());
+  EXPECT_THAT(sign_response.err(), Not(IsEmpty()));
+}
+
+TEST_F(SignatureImplTest, VerifyBadCiphertextFail) {
+  tink_testing_api::SignatureImpl signature;
+  const KeyTemplate& key_template = SignatureKeyTemplates::EcdsaP256();
+  auto private_handle_result = KeysetHandle::GenerateNew(key_template);
+  EXPECT_TRUE(private_handle_result.ok());
+  auto public_handle_result =
+      private_handle_result.ValueOrDie()->GetPublicKeysetHandle();
+  EXPECT_TRUE(public_handle_result.ok());
+
+  SignatureVerifyRequest verify_request;
+  verify_request.set_public_keyset(
+      KeysetBytes(*public_handle_result.ValueOrDie()));
+  verify_request.set_signature("bad signature");
+  verify_request.set_data("some data");
+  SignatureVerifyResponse verify_response;
+
+  EXPECT_TRUE(
+      signature.Verify(nullptr, &verify_request, &verify_response).ok());
+  EXPECT_THAT(verify_response.err(), Not(IsEmpty()));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/testing/cc/streaming_aead_impl.cc b/testing/cc/streaming_aead_impl.cc
new file mode 100644
index 0000000..3dd1a70
--- /dev/null
+++ b/testing/cc/streaming_aead_impl.cc
@@ -0,0 +1,169 @@
+// 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.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Implementation of a StreamingAEAD Service.
+#include "streaming_aead_impl.h"
+
+#include "tink/streaming_aead.h"
+#include "tink/binary_keyset_reader.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "tink/util/istream_input_stream.h"
+#include "tink/util/ostream_output_stream.h"
+#include "tink/util/status.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+namespace tinkutil = ::crypto::tink::util;
+
+using ::crypto::tink::BinaryKeysetReader;
+using ::crypto::tink::CleartextKeysetHandle;
+using ::crypto::tink::InputStream;
+using ::crypto::tink::util::IstreamInputStream;
+using ::crypto::tink::util::OstreamOutputStream;
+using ::grpc::ServerContext;
+using ::grpc::Status;
+
+
+StreamingAeadImpl::StreamingAeadImpl() {}
+
+// Encrypts a message
+::grpc::Status StreamingAeadImpl::Encrypt(
+    grpc::ServerContext* context,
+    const StreamingAeadEncryptRequest* request,
+    StreamingAeadEncryptResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto streaming_aead_result =
+      handle_result.ValueOrDie()->GetPrimitive<crypto::tink::StreamingAead>();
+  if (!streaming_aead_result.ok()) {
+    response->set_err(streaming_aead_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+
+  auto ciphertext_stream = absl::make_unique<std::stringstream>();
+  auto ciphertext_buf = ciphertext_stream->rdbuf();
+  auto ciphertext_destination(
+      absl::make_unique<OstreamOutputStream>(std::move(ciphertext_stream)));
+
+  auto encrypting_stream_result =
+      streaming_aead_result.ValueOrDie()->NewEncryptingStream(
+          std::move(ciphertext_destination), request->associated_data());
+  if (!encrypting_stream_result.ok()) {
+    response->set_err(encrypting_stream_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto encrypting_stream = std::move(encrypting_stream_result.ValueOrDie());
+
+  auto contents = request->plaintext();
+  void* buffer;
+  int pos = 0;
+  int remaining = contents.length();
+  int available_space = 0;
+  int available_bytes = 0;
+  while (remaining > 0) {
+    auto next_result = encrypting_stream->Next(&buffer);
+    if (!next_result.ok()) {
+      response->set_err(next_result.status().error_message());
+      return ::grpc::Status::OK;
+    }
+    available_space = next_result.ValueOrDie();
+    available_bytes = std::min(available_space, remaining);
+    memcpy(buffer, contents.data() + pos, available_bytes);
+    remaining -= available_bytes;
+    pos += available_bytes;
+  }
+  if (available_space > available_bytes) {
+    encrypting_stream->BackUp(available_space - available_bytes);
+  }
+  auto close_status = encrypting_stream->Close();
+  if (!close_status.ok()) {
+     response->set_err(close_status.error_message());
+     return ::grpc::Status::OK;
+  }
+
+  response->set_ciphertext(ciphertext_buf->str());
+  return ::grpc::Status::OK;
+}
+
+// Decrypts a ciphertext
+::grpc::Status StreamingAeadImpl::Decrypt(
+    grpc::ServerContext* context,
+    const StreamingAeadDecryptRequest* request,
+    StreamingAeadDecryptResponse* response) {
+  auto reader_result = BinaryKeysetReader::New(request->keyset());
+  if (!reader_result.ok()) {
+    response->set_err(reader_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto handle_result =
+      CleartextKeysetHandle::Read(std::move(reader_result.ValueOrDie()));
+  if (!handle_result.ok()) {
+    response->set_err(handle_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto streaming_aead_result =
+      handle_result.ValueOrDie()->GetPrimitive<crypto::tink::StreamingAead>();
+  if (!streaming_aead_result.ok()) {
+    response->set_err(streaming_aead_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+
+  auto ciphertext_stream =
+      absl::make_unique<std::stringstream>(request->ciphertext());
+  std::unique_ptr<InputStream> ciphertext_source(
+      absl::make_unique<IstreamInputStream>(std::move(ciphertext_stream)));
+
+  auto decrypting_stream_result =
+      streaming_aead_result.ValueOrDie()->NewDecryptingStream(
+          std::move(ciphertext_source), request->associated_data());
+  if (!decrypting_stream_result.ok()) {
+    response->set_err(decrypting_stream_result.status().error_message());
+    return ::grpc::Status::OK;
+  }
+  auto decrypting_stream = std::move(decrypting_stream_result.ValueOrDie());
+
+  std::string plaintext;
+  const void* buffer;
+  while (true) {
+    auto next_result = decrypting_stream->Next(&buffer);
+    if (next_result.status().error_code() == tinkutil::error::OUT_OF_RANGE) {
+      // End of stream.
+      break;
+    }
+    if (!next_result.ok()) {
+      response->set_err(next_result.status().error_message());
+      return ::grpc::Status::OK;
+    }
+    auto read_bytes = next_result.ValueOrDie();
+    if (read_bytes > 0) {
+      plaintext.append(
+          std::string(reinterpret_cast<const char*>(buffer), read_bytes));
+    }
+  }
+
+  response->set_plaintext(plaintext);
+  return ::grpc::Status::OK;
+}
+
+}  // namespace tink_testing_api
diff --git a/testing/cc/streaming_aead_impl.h b/testing/cc/streaming_aead_impl.h
new file mode 100644
index 0000000..6323cc9
--- /dev/null
+++ b/testing/cc/streaming_aead_impl.h
@@ -0,0 +1,44 @@
+// 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_TESTING_SERIVCES_STREAMING_AEAD_IMPL_H_
+#define TINK_TESTING_SERIVCES_STREAMING_AEAD_IMPL_H_
+
+#include <grpcpp/grpcpp.h>
+#include <grpcpp/server_context.h>
+#include <grpcpp/support/status.h>
+
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace tink_testing_api {
+
+// A StreamingAead Service.
+class StreamingAeadImpl final : public StreamingAead::Service {
+ public:
+  StreamingAeadImpl();
+
+  ~StreamingAeadImpl() override = default;
+
+  grpc::Status Encrypt(grpc::ServerContext* context,
+                       const StreamingAeadEncryptRequest* request,
+                       StreamingAeadEncryptResponse* response) override;
+
+  grpc::Status Decrypt(grpc::ServerContext* context,
+                       const StreamingAeadDecryptRequest* request,
+                       StreamingAeadDecryptResponse* response) override;
+};
+
+}  // namespace tink_testing_api
+
+#endif  // TINK_TESTING_SERIVCES_STREAMING_AEAD_IMPL_H_
diff --git a/testing/cc/streaming_aead_impl_test.cc b/testing/cc/streaming_aead_impl_test.cc
new file mode 100644
index 0000000..7af00ba
--- /dev/null
+++ b/testing/cc/streaming_aead_impl_test.cc
@@ -0,0 +1,120 @@
+// 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 "streaming_aead_impl.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "tink/streamingaead/streaming_aead_config.h"
+#include "tink/streamingaead/streaming_aead_key_templates.h"
+#include "tink/binary_keyset_writer.h"
+#include "tink/cleartext_keyset_handle.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+
+namespace crypto {
+namespace tink {
+namespace {
+
+using ::crypto::tink::StreamingAeadKeyTemplates;
+using ::crypto::tink::BinaryKeysetWriter;
+using ::crypto::tink::CleartextKeysetHandle;
+
+using ::testing::Eq;
+using ::testing::IsEmpty;
+using ::tink_testing_api::StreamingAeadDecryptRequest;
+using ::tink_testing_api::StreamingAeadEncryptRequest;
+using ::tink_testing_api::StreamingAeadEncryptResponse;
+using ::tink_testing_api::StreamingAeadDecryptResponse;
+
+using crypto::tink::KeysetHandle;
+using google::crypto::tink::KeyTemplate;
+
+std::string ValidKeyset() {
+  const KeyTemplate& key_template =
+      StreamingAeadKeyTemplates::Aes128GcmHkdf4KB();
+  auto handle_result = KeysetHandle::GenerateNew(key_template);
+  EXPECT_TRUE(handle_result.ok());
+  std::stringbuf keyset;
+  auto writer_result =
+      BinaryKeysetWriter::New(absl::make_unique<std::ostream>(&keyset));
+  EXPECT_TRUE(writer_result.ok());
+
+  auto status = CleartextKeysetHandle::Write(writer_result.ValueOrDie().get(),
+                                             *handle_result.ValueOrDie());
+  EXPECT_TRUE(status.ok());
+  return keyset.str();
+}
+
+class StreamingAeadImplTest : public ::testing::Test {
+ protected:
+  static void SetUpTestSuite() {
+    ASSERT_TRUE(StreamingAeadConfig::Register().ok());
+  }
+};
+
+TEST_F(StreamingAeadImplTest, EncryptDecryptSuccess) {
+  tink_testing_api::StreamingAeadImpl streaming_aead;
+  std::string keyset = ValidKeyset();
+  StreamingAeadEncryptRequest enc_request;
+  enc_request.set_keyset(keyset);
+  enc_request.set_plaintext("Plain text");
+  enc_request.set_associated_data("ad");
+  StreamingAeadEncryptResponse enc_response;
+
+  EXPECT_TRUE(streaming_aead.Encrypt(nullptr, &enc_request,
+                                     &enc_response).ok());
+  EXPECT_THAT(enc_response.err(), IsEmpty());
+
+  StreamingAeadDecryptRequest dec_request;
+  dec_request.set_keyset(keyset);
+  dec_request.set_ciphertext(enc_response.ciphertext());
+  dec_request.set_associated_data("ad");
+  StreamingAeadDecryptResponse dec_response;
+
+  EXPECT_TRUE(streaming_aead.Decrypt(nullptr, &dec_request,
+                                     &dec_response).ok());
+  EXPECT_THAT(dec_response.err(), IsEmpty());
+  EXPECT_THAT(dec_response.plaintext(), Eq("Plain text"));
+}
+
+TEST_F(StreamingAeadImplTest, EncryptBadKeysetFail) {
+  tink_testing_api::StreamingAeadImpl streaming_aead;
+  StreamingAeadEncryptRequest enc_request;
+  enc_request.set_keyset("bad keyset");
+  enc_request.set_plaintext("Plain text");
+  enc_request.set_associated_data("ad");
+  StreamingAeadEncryptResponse enc_response;
+
+  EXPECT_TRUE(streaming_aead.Encrypt(nullptr, &enc_request,
+                                     &enc_response).ok());
+  EXPECT_THAT(enc_response.err(), Not(IsEmpty()));
+}
+
+TEST_F(StreamingAeadImplTest, DecryptBadCiphertextFail) {
+  tink_testing_api::StreamingAeadImpl streaming_aead;
+  std::string keyset = ValidKeyset();
+  StreamingAeadDecryptRequest dec_request;
+  dec_request.set_keyset(keyset);
+  dec_request.set_ciphertext("bad ciphertext");
+  dec_request.set_associated_data("ad");
+  StreamingAeadDecryptResponse dec_response;
+
+  EXPECT_TRUE(streaming_aead.Decrypt(nullptr, &dec_request,
+                                     &dec_response).ok());
+  EXPECT_THAT(dec_response.err(), Not(IsEmpty()));
+}
+
+}  // namespace
+}  // namespace tink
+}  // namespace crypto
diff --git a/testing/cc/testing_server.cc b/testing/cc/testing_server.cc
new file mode 100644
index 0000000..452b9ed
--- /dev/null
+++ b/testing/cc/testing_server.cc
@@ -0,0 +1,73 @@
+// 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 <grpcpp/grpcpp.h>
+
+#include "absl/flags/flag.h"
+#include "absl/flags/parse.h"
+#include "tink/config/tink_config.h"
+#include "proto/testing/testing_api.grpc.pb.h"
+#include "aead_impl.h"
+#include "deterministic_aead_impl.h"
+#include "hybrid_impl.h"
+#include "keyset_impl.h"
+#include "mac_impl.h"
+#include "metadata_impl.h"
+#include "signature_impl.h"
+#include "streaming_aead_impl.h"
+
+ABSL_FLAG(int, port, 23456, "the port");
+
+void RunServer() {
+  auto status = crypto::tink::TinkConfig::Register();
+  if (!status.ok()) {
+    std::cout << "TinkConfig::Register() failed: " << status.error_message()
+              << std::endl;
+    return;
+  }
+  const int port = absl::GetFlag(FLAGS_port);
+  std::string server_address = absl::StrCat("[::]:", port);
+
+  tink_testing_api::MetadataImpl metadata;
+  tink_testing_api::KeysetImpl keyset;
+  tink_testing_api::AeadImpl aead;
+  tink_testing_api::DeterministicAeadImpl deterministic_aead;
+  tink_testing_api::HybridImpl hybrid;
+  tink_testing_api::MacImpl mac;
+  tink_testing_api::SignatureImpl signature;
+  tink_testing_api::StreamingAeadImpl streaming_aead;
+
+  grpc::ServerBuilder builder;
+  builder.AddListeningPort(
+      server_address, ::grpc::experimental::LocalServerCredentials(LOCAL_TCP));
+
+  builder.RegisterService(&metadata);
+  builder.RegisterService(&keyset);
+  builder.RegisterService(&aead);
+  builder.RegisterService(&deterministic_aead);
+  builder.RegisterService(&hybrid);
+  builder.RegisterService(&mac);
+  builder.RegisterService(&signature);
+  builder.RegisterService(&streaming_aead);
+
+  std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
+  std::cout << "Server listening on " << server_address << std::endl;
+  server->Wait();
+}
+
+int main(int argc, char** argv) {
+  absl::ParseCommandLine(argc, argv);
+  RunServer();
+  return 0;
+}
diff --git a/testing/cross_language/.bazelversion b/testing/cross_language/.bazelversion
new file mode 100644
index 0000000..fd2a018
--- /dev/null
+++ b/testing/cross_language/.bazelversion
@@ -0,0 +1 @@
+3.1.0
diff --git a/testing/cross_language/BUILD.bazel b/testing/cross_language/BUILD.bazel
new file mode 100644
index 0000000..e1ed097
--- /dev/null
+++ b/testing/cross_language/BUILD.bazel
@@ -0,0 +1,128 @@
+load("@pip_deps//:requirements.bzl", "requirement")
+
+package(
+    default_testonly = 1,
+    default_visibility = ["//:__subpackages__"],
+)
+
+licenses(["notice"])
+
+py_test(
+    name = "key_generation_consistency_test",
+    srcs = ["key_generation_consistency_test.py"],
+    deps = [
+        "//util:supported_key_types",
+        "//util:testing_servers",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/aead",
+        "@tink_py//tink/daead",
+        "@tink_py//tink/hybrid",
+        "@tink_py//tink/mac",
+        "@tink_py//tink/proto:common_py_pb2",
+        "@tink_py//tink/proto:ecdsa_py_pb2",
+        "@tink_py//tink/signature",
+    ],
+)
+
+py_test(
+    name = "json_test",
+    srcs = ["json_test.py"],
+    deps = [
+        "//util:supported_key_types",
+        "//util:testing_servers",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+    ],
+)
+
+py_test(
+    name = "aead_test",
+    srcs = ["aead_test.py"],
+    deps = [
+        "//util:keyset_builder",
+        "//util:supported_key_types",
+        "//util:testing_servers",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/aead",
+        "@tink_py//tink/proto:tink_py_pb2",
+    ],
+)
+
+py_test(
+    name = "aead_consistency_test",
+    srcs = ["aead_consistency_test.py"],
+    deps = [
+        "//util:supported_key_types",
+        "//util:testing_servers",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/aead",
+        "@tink_py//tink/proto:aes_ctr_hmac_aead_py_pb2",
+        "@tink_py//tink/proto:aes_eax_py_pb2",
+        "@tink_py//tink/proto:aes_gcm_py_pb2",
+        "@tink_py//tink/proto:common_py_pb2",
+        "@tink_py//tink/proto:tink_py_pb2",
+    ],
+)
+
+py_test(
+    name = "deterministic_aead_test",
+    srcs = ["deterministic_aead_test.py"],
+    deps = [
+        "//util:supported_key_types",
+        "//util:testing_servers",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/daead",
+    ],
+)
+
+py_test(
+    name = "streaming_aead_test",
+    srcs = ["streaming_aead_test.py"],
+    deps = [
+        "//util:supported_key_types",
+        "//util:testing_servers",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/streaming_aead",
+    ],
+)
+
+py_test(
+    name = "mac_test",
+    srcs = ["mac_test.py"],
+    deps = [
+        "//util:supported_key_types",
+        "//util:testing_servers",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/mac",
+    ],
+)
+
+py_test(
+    name = "signature_test",
+    srcs = ["signature_test.py"],
+    deps = [
+        "//util:supported_key_types",
+        "//util:testing_servers",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/signature",
+    ],
+)
+
+py_test(
+    name = "hybrid_encryption_test",
+    srcs = ["hybrid_encryption_test.py"],
+    deps = [
+        "//util:supported_key_types",
+        "//util:testing_servers",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/hybrid",
+    ],
+)
diff --git a/testing/cross_language/WORKSPACE b/testing/cross_language/WORKSPACE
new file mode 100644
index 0000000..8794129
--- /dev/null
+++ b/testing/cross_language/WORKSPACE
@@ -0,0 +1,99 @@
+workspace(name = "testing_python")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+local_repository(
+    name = "tink_base",
+    path = "../..",
+)
+
+local_repository(
+    name = "tink_py",
+    path = "../../python",
+)
+
+local_repository(
+    name = "tink_cc",
+    path = "../../cc",
+)
+
+# NOTE: The Python dependencies have to be loaded first, as they rely on a
+# newer version of rules_python.
+load("@tink_py//:tink_py_deps.bzl", "tink_py_deps")
+tink_py_deps()
+
+load("@tink_py//:tink_py_deps_init.bzl", "tink_py_deps_init")
+tink_py_deps_init("tink_py")
+
+load("@tink_py_pip_deps//:requirements.bzl", tink_pip_install = "pip_install")
+tink_pip_install()
+
+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()
+
+load("@tink_cc//:tink_cc_deps.bzl", "tink_cc_deps")
+tink_cc_deps()
+
+load("@tink_cc//:tink_cc_deps_init.bzl", "tink_cc_deps_init")
+tink_cc_deps_init()
+
+http_archive(
+    name = "rules_python",
+    strip_prefix = "rules_python-94677401bc56ed5d756f50b441a6a5c7f735a6d4",
+    url = "https://github.com/bazelbuild/rules_python/archive/94677401bc56ed5d756f50b441a6a5c7f735a6d4.zip",
+    sha256 = "de39bc4d6605e6d395faf5e07516c64c8d833404ee3eb132b5ff1161f9617dec",
+)
+
+http_archive(
+    name = "rules_proto_grpc",
+    urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/1.0.2.tar.gz"],
+    sha256 = "5f0f2fc0199810c65a2de148a52ba0aff14d631d4e8202f41aff6a9d590a471b",
+    strip_prefix = "rules_proto_grpc-1.0.2",
+)
+
+http_archive(
+    name = "org_python_pypi_portpicker",
+    urls = [
+        "https://pypi.python.org/packages/d9/f4/0188bc07d38b5f9dd192b8329c5e098e3b23552c01a96fd08973dba9e315/portpicker-1.3.1.tar.gz",
+    ],
+    sha256 = "d2cdc776873635ed421315c4d22e63280042456bbfa07397817e687b142b9667",
+    strip_prefix = "portpicker-1.3.1/src",
+    build_file = "portpicker.BUILD.bazel",
+)
+
+load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_toolchains", "rules_proto_grpc_repos")
+rules_proto_grpc_toolchains()
+rules_proto_grpc_repos()
+
+load("@rules_proto_grpc//python:repositories.bzl", rules_proto_grpc_python_repos="python_repos")
+rules_proto_grpc_python_repos()
+
+load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
+
+grpc_deps()
+
+load("@rules_python//python:repositories.bzl", "py_repositories")
+py_repositories()
+
+load("@rules_python//python:pip.bzl", "pip_repositories", "pip3_import")
+pip_repositories()
+
+pip3_import(
+   name = "pip_deps",
+   requirements = "//:requirements.txt",
+)
+
+load("@pip_deps//:requirements.bzl", "pip_install")
+pip_install()
+
+pip3_import(
+    name = "rules_proto_grpc_py3_deps",
+    requirements = "@rules_proto_grpc//python:requirements.txt",
+)
+
+load("@rules_proto_grpc_py3_deps//:requirements.bzl", pip3_install="pip_install")
+pip3_install()
+
diff --git a/testing/cross_language/aead_consistency_test.py b/testing/cross_language/aead_consistency_test.py
new file mode 100644
index 0000000..24a7942
--- /dev/null
+++ b/testing/cross_language/aead_consistency_test.py
@@ -0,0 +1,203 @@
+# 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.
+"""Cross-language consistency tests for AEAD."""
+
+import itertools
+
+from absl.testing import absltest
+from absl.testing import parameterized
+
+import tink
+from tink import aead
+
+from tink.proto import aes_ctr_hmac_aead_pb2
+from tink.proto import aes_eax_pb2
+from tink.proto import aes_gcm_pb2
+from tink.proto import common_pb2
+from tink.proto import tink_pb2
+from util import supported_key_types
+from util import testing_servers
+
+HASH_TYPES = [
+    common_pb2.UNKNOWN_HASH,
+    common_pb2.SHA1,
+    common_pb2.SHA256,
+    common_pb2.SHA384,
+    common_pb2.SHA512
+]
+
+# Test cases that succeed in a language but should fail
+SUCCEEDS_BUT_SHOULD_FAIL = [
+    # TODO(b/159989251)
+    # HMAC with SHA384 is accepted in go, but not in other langs.
+    ('AesCtrHmacAeadKey(16,16,16,16,SHA384,0,0,0)', 'go'),
+]
+
+# Test cases that fail in a language but should succeed
+FAILS_BUT_SHOULD_SUCCEED = [
+]
+
+
+def setUpModule():
+  aead.register()
+  testing_servers.start()
+
+
+def tearDownModule():
+  testing_servers.stop()
+
+
+def _gen_keyset(
+    type_url: str, value: bytes,
+    key_material_type: tink_pb2.KeyData.KeyMaterialType) -> tink_pb2.Keyset:
+  """Generates a new Keyset."""
+  keyset = tink_pb2.Keyset()
+  key = keyset.key.add()
+  key.key_data.type_url = type_url
+  key.key_data.value = value
+  key.key_data.key_material_type = key_material_type
+  key.status = tink_pb2.ENABLED
+  key.key_id = 42
+  key.output_prefix_type = tink_pb2.TINK
+  keyset.primary_key_id = 42
+  return keyset
+
+
+def _gen_key_value(size: int) -> bytes:
+  """Returns a fixed key_value of a given size."""
+  return bytes(i for i in range(size))
+
+
+def aes_eax_key_test_cases():
+  def _test_case(key_size=16, iv_size=16, key_version=0):
+    key = aes_eax_pb2.AesEaxKey()
+    key.version = key_version
+    key.key_value = _gen_key_value(key_size)
+    key.params.iv_size = iv_size
+    keyset = _gen_keyset(
+        'type.googleapis.com/google.crypto.tink.AesEaxKey',
+        key.SerializeToString(),
+        tink_pb2.KeyData.SYMMETRIC)
+    return ('AesEaxKey(%d,%d,%d)' % (key_size, iv_size, key_version), keyset)
+  for key_size in [15, 16, 24, 32, 64, 96]:
+    for iv_size in [11, 12, 16, 17, 24, 32]:
+      yield _test_case(key_size=key_size, iv_size=iv_size)
+  yield _test_case(key_version=1)
+
+
+def aes_gcm_key_test_cases():
+  def _test_case(key_size=16, key_version=0):
+    key = aes_gcm_pb2.AesGcmKey()
+    key.version = key_version
+    key.key_value = _gen_key_value(key_size)
+    keyset = _gen_keyset(
+        'type.googleapis.com/google.crypto.tink.AesGcmKey',
+        key.SerializeToString(),
+        tink_pb2.KeyData.SYMMETRIC)
+    return ('AesGcmKey(%d,%d)' % (key_size, key_version), keyset)
+  for key_size in [15, 16, 24, 32, 64, 96]:
+    yield _test_case(key_size=key_size)
+  yield _test_case(key_version=1)
+
+
+def aes_ctr_hmac_aead_key_test_cases():
+  def _test_case(aes_key_size=16,
+                 iv_size=16,
+                 hmac_key_size=16,
+                 hmac_tag_size=16,
+                 hash_type=common_pb2.SHA256,
+                 key_version=0,
+                 aes_ctr_version=0,
+                 hmac_version=0):
+    key = aes_ctr_hmac_aead_pb2.AesCtrHmacAeadKey()
+    key.version = key_version
+    key.aes_ctr_key.version = aes_ctr_version
+    key.aes_ctr_key.params.iv_size = iv_size
+    key.aes_ctr_key.key_value = _gen_key_value(aes_key_size)
+    key.hmac_key.version = hmac_version
+    key.hmac_key.params.tag_size = hmac_tag_size
+    key.hmac_key.params.hash = hash_type
+    key.hmac_key.key_value = _gen_key_value(hmac_key_size)
+    keyset = _gen_keyset(
+        'type.googleapis.com/google.crypto.tink.AesCtrHmacAeadKey',
+        key.SerializeToString(),
+        tink_pb2.KeyData.SYMMETRIC)
+    return ('AesCtrHmacAeadKey(%d,%d,%d,%d,%s,%d,%d,%d)' %
+            (aes_key_size, iv_size, hmac_key_size, hmac_tag_size,
+             common_pb2.HashType.Name(hash_type),
+             key_version, aes_ctr_version, hmac_version), keyset)
+  yield _test_case()
+  for aes_key_size in [15, 16, 24, 32, 64, 96]:
+    for iv_size in [11, 12, 16, 17, 24, 32]:
+      yield _test_case(aes_key_size=aes_key_size, iv_size=iv_size)
+  for hmac_key_size in [15, 16, 24, 32, 64, 96]:
+    for hmac_tag_size in [9, 10, 16, 20, 21, 24, 32, 33, 64, 65]:
+      yield _test_case(hmac_key_size=hmac_key_size,
+                       hmac_tag_size=hmac_tag_size)
+  for hash_type in HASH_TYPES:
+    yield _test_case(hash_type=hash_type)
+  yield _test_case(key_version=1)
+  yield _test_case(aes_ctr_version=1)
+  yield _test_case(hmac_version=1)
+
+
+class AeadKeyConsistencyTest(parameterized.TestCase):
+
+  @parameterized.parameters(
+      itertools.chain(aes_eax_key_test_cases(),
+                      aes_gcm_key_test_cases(),
+                      aes_ctr_hmac_aead_key_test_cases()))
+  def test_keyset_validation_consistency(self, name, keyset):
+    supported_langs = supported_key_types.SUPPORTED_LANGUAGES_PER_TYPE_URL[
+        keyset.key[0].key_data.type_url]
+    supported_aeads = [
+        testing_servers.aead(lang, keyset.SerializeToString())
+        for lang in supported_langs
+    ]
+    plaintext = b'plaintext'
+    associated_data = b'associated_data'
+    failures = 0
+    ciphertexts = {}
+    results = {}
+    for p in supported_aeads:
+      try:
+        ciphertexts[p.lang] = p.encrypt(plaintext, associated_data)
+        if (name, p.lang) in SUCCEEDS_BUT_SHOULD_FAIL:
+          failures += 1
+          del ciphertexts[p.lang]
+        if (name, p.lang) in FAILS_BUT_SHOULD_SUCCEED:
+          self.fail('(%s, %s) succeeded, but is in FAILS_BUT_SHOULD_SUCCEED' %
+                    (name, p.lang))
+        results[p.lang] = 'success'
+      except tink.TinkError as e:
+        if (name, p.lang) not in FAILS_BUT_SHOULD_SUCCEED:
+          failures += 1
+        if (name, p.lang) in SUCCEEDS_BUT_SHOULD_FAIL:
+          self.fail(
+              '(%s, %s) is in SUCCEEDS_BUT_SHOULD_FAIL, but failed with %s' %
+              (name, p.lang, e))
+        results[p.lang] = e
+    # Test that either all supported langs accept the key, or all reject.
+    if failures not in [0, len(supported_langs)]:
+      self.fail('encryption for key %s is inconsistent: %s' %
+                (name, results))
+    # Test all generated ciphertexts can be decypted.
+    for enc_lang, ciphertext in ciphertexts.items():
+      dec_aead = supported_aeads[0]
+      output = dec_aead.decrypt(ciphertext, associated_data)
+      if output != plaintext:
+        self.fail('ciphertext encrypted with key %s in lang %s could not be'
+                  'decrypted in lang %s.' % (name, enc_lang, dec_aead.lang))
+
+
+if __name__ == '__main__':
+  absltest.main()
diff --git a/testing/cross_language/aead_test.py b/testing/cross_language/aead_test.py
new file mode 100644
index 0000000..f1dd417
--- /dev/null
+++ b/testing/cross_language/aead_test.py
@@ -0,0 +1,138 @@
+# 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.
+"""Cross-language tests for the Aead primitive."""
+
+from absl.testing import absltest
+from absl.testing import parameterized
+
+import tink
+from tink import aead
+
+from tink.proto import tink_pb2
+from util import keyset_builder
+from util import supported_key_types
+from util import testing_servers
+
+SUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE['aead']
+
+
+def key_rotation_test_cases():
+  for enc_lang in SUPPORTED_LANGUAGES:
+    for dec_lang in SUPPORTED_LANGUAGES:
+      for prefix in [tink_pb2.RAW, tink_pb2.TINK]:
+        old_key_tmpl = aead.aead_key_templates.create_aes_gcm_key_template(16)
+        old_key_tmpl.output_prefix_type = prefix
+        new_key_tmpl = aead.aead_key_templates.AES128_CTR_HMAC_SHA256
+        yield (enc_lang, dec_lang, old_key_tmpl, new_key_tmpl)
+
+
+def setUpModule():
+  aead.register()
+  testing_servers.start()
+
+
+def tearDownModule():
+  testing_servers.stop()
+
+
+class AeadPythonTest(parameterized.TestCase):
+
+  @parameterized.parameters(
+      supported_key_types.test_cases(supported_key_types.AEAD_KEY_TYPES))
+  def test_encrypt_decrypt(self, key_template_name, supported_langs):
+    key_template = supported_key_types.KEY_TEMPLATE[key_template_name]
+    # use java to generate keys, as it supports all key types.
+    keyset = testing_servers.new_keyset('java', key_template)
+    supported_aeads = [
+        testing_servers.aead(lang, keyset) for lang in supported_langs
+    ]
+    unsupported_aeads = [
+        testing_servers.aead(lang, keyset)
+        for lang in SUPPORTED_LANGUAGES
+        if lang not in supported_langs
+    ]
+    for p in supported_aeads:
+      plaintext = (
+          b'This is some plaintext message to be encrypted using key_template '
+          b'%s using %s for encryption.'
+          % (key_template_name.encode('utf8'), p.lang.encode('utf8')))
+      associated_data = (
+          b'Some associated data for %s using %s for encryption.' %
+          (key_template_name.encode('utf8'), p.lang.encode('utf8')))
+      ciphertext = p.encrypt(plaintext, associated_data)
+      for p2 in supported_aeads:
+        output = p2.decrypt(ciphertext, associated_data)
+        self.assertEqual(output, plaintext)
+      for p2 in unsupported_aeads:
+        with self.assertRaises(tink.TinkError):
+          p2.decrypt(ciphertext, associated_data)
+    for p in unsupported_aeads:
+      with self.assertRaises(tink.TinkError):
+        p.encrypt(b'plaintext', b'associated_data')
+
+  @parameterized.parameters(key_rotation_test_cases())
+  def test_key_rotation(self, enc_lang, dec_lang, old_key_tmpl, new_key_tmpl):
+    # Do a key rotation from an old key generated from old_key_tmpl to a new
+    # key generated from new_key_tmpl. Encryption and decryption are done
+    # in languages enc_lang and dec_lang.
+    builder = keyset_builder.new_keyset_builder()
+    older_key_id = builder.add_new_key(old_key_tmpl)
+    builder.set_primary_key(older_key_id)
+    enc_aead1 = testing_servers.aead(enc_lang, builder.keyset())
+    dec_aead1 = testing_servers.aead(dec_lang, builder.keyset())
+    newer_key_id = builder.add_new_key(new_key_tmpl)
+    enc_aead2 = testing_servers.aead(enc_lang, builder.keyset())
+    dec_aead2 = testing_servers.aead(dec_lang, builder.keyset())
+
+    builder.set_primary_key(newer_key_id)
+    enc_aead3 = testing_servers.aead(enc_lang, builder.keyset())
+    dec_aead3 = testing_servers.aead(dec_lang, builder.keyset())
+
+    builder.disable_key(older_key_id)
+    enc_aead4 = testing_servers.aead(enc_lang, builder.keyset())
+    dec_aead4 = testing_servers.aead(dec_lang, builder.keyset())
+
+    self.assertNotEqual(older_key_id, newer_key_id)
+    # 1 encrypts with the older key. So 1, 2 and 3 can decrypt it, but not 4.
+    ciphertext1 = enc_aead1.encrypt(b'plaintext', b'ad')
+    self.assertEqual(dec_aead1.decrypt(ciphertext1, b'ad'), b'plaintext')
+    self.assertEqual(dec_aead2.decrypt(ciphertext1, b'ad'), b'plaintext')
+    self.assertEqual(dec_aead3.decrypt(ciphertext1, b'ad'), b'plaintext')
+    with self.assertRaises(tink.TinkError):
+      _ = dec_aead4.decrypt(ciphertext1, b'ad')
+
+    # 2 encrypts with the older key. So 1, 2 and 3 can decrypt it, but not 4.
+    ciphertext2 = enc_aead2.encrypt(b'plaintext', b'ad')
+    self.assertEqual(dec_aead1.decrypt(ciphertext2, b'ad'), b'plaintext')
+    self.assertEqual(dec_aead2.decrypt(ciphertext2, b'ad'), b'plaintext')
+    self.assertEqual(dec_aead3.decrypt(ciphertext2, b'ad'), b'plaintext')
+    with self.assertRaises(tink.TinkError):
+      _ = dec_aead4.decrypt(ciphertext2, b'ad')
+
+    # 3 encrypts with the newer key. So 2, 3 and 4 can decrypt it, but not 1.
+    ciphertext3 = enc_aead3.encrypt(b'plaintext', b'ad')
+    with self.assertRaises(tink.TinkError):
+      _ = dec_aead1.decrypt(ciphertext3, b'ad')
+    self.assertEqual(dec_aead2.decrypt(ciphertext3, b'ad'), b'plaintext')
+    self.assertEqual(dec_aead3.decrypt(ciphertext3, b'ad'), b'plaintext')
+    self.assertEqual(dec_aead4.decrypt(ciphertext3, b'ad'), b'plaintext')
+
+    # 4 encrypts with the newer key. So 2, 3 and 4 can decrypt it, but not 1.
+    ciphertext4 = enc_aead4.encrypt(b'plaintext', b'ad')
+    with self.assertRaises(tink.TinkError):
+      _ = dec_aead1.decrypt(ciphertext4, b'ad')
+    self.assertEqual(dec_aead2.decrypt(ciphertext4, b'ad'), b'plaintext')
+    self.assertEqual(dec_aead3.decrypt(ciphertext4, b'ad'), b'plaintext')
+    self.assertEqual(dec_aead4.decrypt(ciphertext4, b'ad'), b'plaintext')
+
+if __name__ == '__main__':
+  absltest.main()
diff --git a/tools/testing/cross_language/deterministic_aead_test.py b/testing/cross_language/deterministic_aead_test.py
similarity index 83%
rename from tools/testing/cross_language/deterministic_aead_test.py
rename to testing/cross_language/deterministic_aead_test.py
index 90aa6a9..9d3d29f 100644
--- a/tools/testing/cross_language/deterministic_aead_test.py
+++ b/testing/cross_language/deterministic_aead_test.py
@@ -16,13 +16,19 @@
 
 import tink
 from tink import daead
-from tools.testing import supported_key_types
-from tools.testing.cross_language.util import cli_daead
-from tools.testing.cross_language.util import keyset_manager
+from util import supported_key_types
+from util import testing_servers
+
+SUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE['daead']
 
 
 def setUpModule():
   daead.register()
+  testing_servers.start()
+
+
+def tearDownModule():
+  testing_servers.stop()
 
 
 class DeterministicAeadTest(parameterized.TestCase):
@@ -31,15 +37,15 @@
       supported_key_types.test_cases(supported_key_types.DAEAD_KEY_TYPES))
   def test_encrypt_decrypt(self, key_template_name, supported_langs):
     key_template = supported_key_types.KEY_TEMPLATE[key_template_name]
-    keyset_handle = keyset_manager.new_keyset_handle(key_template)
+    keyset = testing_servers.new_keyset('java', key_template)
     supported_daeads = [
-        cli_daead.CliDeterministicAead(lang, keyset_handle)
+        testing_servers.deterministic_aead(lang, keyset)
         for lang in supported_langs
     ]
     self.assertNotEmpty(supported_daeads)
     unsupported_daeads = [
-        cli_daead.CliDeterministicAead(lang, keyset_handle)
-        for lang in cli_daead.LANGUAGES
+        testing_servers.deterministic_aead(lang, keyset)
+        for lang in SUPPORTED_LANGUAGES
         if lang not in supported_langs
     ]
     plaintext = (
diff --git a/testing/cross_language/external/portpicker.BUILD.bazel b/testing/cross_language/external/portpicker.BUILD.bazel
new file mode 100644
index 0000000..57e2ec3
--- /dev/null
+++ b/testing/cross_language/external/portpicker.BUILD.bazel
@@ -0,0 +1,13 @@
+# Description:
+#   Import of portpicker library.
+
+licenses(["notice"])
+
+exports_files(["LICENSE"])
+
+py_library(
+    name = "portpicker",
+    srcs = glob(["*.py"]),
+    srcs_version = "PY2AND3",
+    visibility = ["//visibility:public"],
+)
diff --git a/tools/testing/cross_language/hybrid_encryption_test.py b/testing/cross_language/hybrid_encryption_test.py
similarity index 76%
rename from tools/testing/cross_language/hybrid_encryption_test.py
rename to testing/cross_language/hybrid_encryption_test.py
index 36476bc..71cfc10 100644
--- a/tools/testing/cross_language/hybrid_encryption_test.py
+++ b/testing/cross_language/hybrid_encryption_test.py
@@ -17,13 +17,19 @@
 import tink
 from tink import hybrid
 
-from tools.testing import supported_key_types
-from tools.testing.cross_language.util import cli_hybrid
-from tools.testing.cross_language.util import keyset_manager
+from util import supported_key_types
+from util import testing_servers
+
+SUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE['hybrid']
 
 
 def setUpModule():
   hybrid.register()
+  testing_servers.start()
+
+
+def tearDownModule():
+  testing_servers.stop()
 
 
 class HybridEncryptionTest(parameterized.TestCase):
@@ -33,24 +39,24 @@
           supported_key_types.HYBRID_PRIVATE_KEY_TYPES))
   def test_encrypt_decrypt(self, key_template_name, supported_langs):
     key_template = supported_key_types.KEY_TEMPLATE[key_template_name]
-    private_handle = keyset_manager.new_keyset_handle(key_template)
+    private_keyset = testing_servers.new_keyset('java', key_template)
     supported_decs = [
-        cli_hybrid.CliHybridDecrypt(lang, private_handle)
+        testing_servers.hybrid_decrypt(lang, private_keyset)
         for lang in supported_langs
     ]
     unsupported_decs = [
-        cli_hybrid.CliHybridDecrypt(lang, private_handle)
-        for lang in cli_hybrid.LANGUAGES
+        testing_servers.hybrid_decrypt(lang, private_keyset)
+        for lang in SUPPORTED_LANGUAGES
         if lang not in supported_langs
     ]
-    public_handle = private_handle.public_keyset_handle()
+    public_keyset = testing_servers.public_keyset('java', private_keyset)
     supported_encs = [
-        cli_hybrid.CliHybridEncrypt(lang, public_handle)
+        testing_servers.hybrid_encrypt(lang, public_keyset)
         for lang in supported_langs
     ]
     unsupported_encs = [
-        cli_hybrid.CliHybridEncrypt(lang, public_handle)
-        for lang in cli_hybrid.LANGUAGES
+        testing_servers.hybrid_encrypt(lang, public_keyset)
+        for lang in testing_servers.LANGUAGES
         if lang not in supported_langs
     ]
     for enc in supported_encs:
diff --git a/testing/cross_language/json_test.py b/testing/cross_language/json_test.py
new file mode 100644
index 0000000..d755253
--- /dev/null
+++ b/testing/cross_language/json_test.py
@@ -0,0 +1,95 @@
+# 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.
+"""Cross-language tests for JSON serialization."""
+
+from absl.testing import absltest
+from absl.testing import parameterized
+
+from tink.proto import tink_pb2
+from util import supported_key_types
+from util import testing_servers
+
+
+def setUpModule():
+  testing_servers.start()
+
+
+def tearDownModule():
+  testing_servers.stop()
+
+
+def _keyset_proto(keyset: bytes) -> tink_pb2.Keyset:
+  keyset_proto = tink_pb2.Keyset()
+  keyset_proto.ParseFromString(keyset)
+  # We sort the keys, since we want keysets to be considered equal even if the
+  # keys are in different order.
+  keyset_proto.key.sort(key=lambda k: k.key_id)
+  return keyset_proto
+
+
+def _is_equal_keyset(keyset1: bytes, keyset2: bytes) -> bool:
+  """Checks if two keyset are equal, and have the exact same keydata.value."""
+  # Keydata.value are serialized protos. This serialization is usually not
+  # deterministic, as it is a unsorted list of key value pairs.
+  # But since JSON serialization does not change keyset.value, we can simply
+  # require these values to be exactly the same in this test. In other tests,
+  # this might be too strict.
+  return _keyset_proto(keyset1) == _keyset_proto(keyset2)
+
+
+class JsonTest(parameterized.TestCase):
+
+  def test_is_equal_keyset(self):
+    keyset1 = tink_pb2.Keyset()
+    key11 = keyset1.key.add()
+    key11.key_id = 21
+    key12 = keyset1.key.add()
+    key12.key_id = 42
+    keyset2 = tink_pb2.Keyset()
+    key21 = keyset2.key.add()
+    key21.key_id = 42
+    key22 = keyset2.key.add()
+    key22.key_id = 21
+    self.assertTrue(_is_equal_keyset(keyset1.SerializeToString(),
+                                     keyset2.SerializeToString()))
+
+  def test_is_not_equal_keyset(self):
+    keyset1 = tink_pb2.Keyset()
+    key11 = keyset1.key.add()
+    key11.key_id = 21
+    key12 = keyset1.key.add()
+    key12.key_id = 42
+    keyset2 = tink_pb2.Keyset()
+    key3 = keyset2.key.add()
+    key3.key_id = 21
+    self.assertFalse(_is_equal_keyset(keyset1.SerializeToString(),
+                                      keyset2.SerializeToString()))
+
+  def assertEqualKeyset(self, keyset1: bytes, keyset2: bytes):
+    if not _is_equal_keyset(keyset1, keyset2):
+      self.fail('these keysets are not equal: \n%s\n \n%s\n'
+                % (_keyset_proto(keyset1), _keyset_proto(keyset2)))
+
+  @parameterized.parameters(
+      supported_key_types.test_cases(supported_key_types.ALL_KEY_TYPES))
+  def test_to_from_json(self, key_template_name, supported_langs):
+    key_template = supported_key_types.KEY_TEMPLATE[key_template_name]
+    keyset = testing_servers.new_keyset('java', key_template)
+    for to_lang in supported_langs:
+      json_keyset = testing_servers.keyset_to_json(to_lang, keyset)
+      for from_lang in supported_langs:
+        keyset2 = testing_servers.keyset_from_json(from_lang, json_keyset)
+        self.assertEqualKeyset(keyset, keyset2)
+
+
+if __name__ == '__main__':
+  absltest.main()
diff --git a/testing/cross_language/key_generation_consistency_test.py b/testing/cross_language/key_generation_consistency_test.py
new file mode 100644
index 0000000..6f75bc1
--- /dev/null
+++ b/testing/cross_language/key_generation_consistency_test.py
@@ -0,0 +1,339 @@
+# 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 tink.testing.cross_language.key_generation_consistency."""
+
+import itertools
+
+from absl.testing import absltest
+from absl.testing import parameterized
+
+import tink
+from tink import aead
+from tink import daead
+from tink import hybrid
+from tink import mac
+from tink import signature
+
+from tink.proto import common_pb2
+from tink.proto import ecdsa_pb2
+from util import supported_key_types
+from util import testing_servers
+
+TYPE_URL_TO_SUPPORTED_LANGUAGES = {
+    'type.googleapis.com/google.crypto.tink.' + key_type: langs
+    for key_type, langs in supported_key_types.SUPPORTED_LANGUAGES.items()
+}
+
+# Test cases that succeed in a language but should fail
+SUCCEEDS_BUT_SHOULD_FAIL = [
+    # TODO(b/159989251)
+    # HMAC with SHA384 is accepted in go, but not in other langs.
+    ('HmacKey(32,10,SHA384)', 'go'),
+    ('HmacKey(32,16,SHA384)', 'go'),
+    ('HmacKey(32,20,SHA384)', 'go'),
+    ('HmacKey(32,21,SHA384)', 'go'),
+    ('HmacKey(32,24,SHA384)', 'go'),
+    ('HmacKey(32,32,SHA384)', 'go'),
+    ('HmacKey(32,33,SHA384)', 'go'),
+    # TODO(b/160130470): In CC and Python Hybrid templates are not checked for
+    # valid AEAD params. (These params *are* checked when the key is used.)
+    ('EciesAeadHkdfPrivateKey(NIST_P256,UNCOMPRESSED,SHA256,AesEaxKey(15,11))',
+     'cc'),
+    ('EciesAeadHkdfPrivateKey(NIST_P256,UNCOMPRESSED,SHA256,AesEaxKey(15,11))',
+     'python'),
+]
+
+# Test cases that fail in a language but should succeed
+FAILS_BUT_SHOULD_SUCCEED = [
+    # TODO(b/160134058) Java and Go do not accept templates with CURVE25519.
+    ('EciesAeadHkdfPrivateKey(CURVE25519,UNCOMPRESSED,SHA1,AesGcmKey(16))',
+     'java'),
+    ('EciesAeadHkdfPrivateKey(CURVE25519,UNCOMPRESSED,SHA1,AesGcmKey(16))',
+     'go'),
+    ('EciesAeadHkdfPrivateKey(CURVE25519,UNCOMPRESSED,SHA256,AesGcmKey(16))',
+     'java'),
+    ('EciesAeadHkdfPrivateKey(CURVE25519,UNCOMPRESSED,SHA256,AesGcmKey(16))',
+     'go'),
+    ('EciesAeadHkdfPrivateKey(CURVE25519,UNCOMPRESSED,SHA384,AesGcmKey(16))',
+     'java'),
+    ('EciesAeadHkdfPrivateKey(CURVE25519,UNCOMPRESSED,SHA384,AesGcmKey(16))',
+     'go'),
+    ('EciesAeadHkdfPrivateKey(CURVE25519,UNCOMPRESSED,SHA512,AesGcmKey(16))',
+     'java'),
+    ('EciesAeadHkdfPrivateKey(CURVE25519,UNCOMPRESSED,SHA512,AesGcmKey(16))',
+     'go'),
+    # TODO(b/160132617) Java does not accept templates with hash type SHA384.
+    ('EciesAeadHkdfPrivateKey(NIST_P256,UNCOMPRESSED,SHA384,AesGcmKey(16))',
+     'java'),
+    ('EciesAeadHkdfPrivateKey(NIST_P384,UNCOMPRESSED,SHA384,AesGcmKey(16))',
+     'java'),
+    ('EciesAeadHkdfPrivateKey(NIST_P521,UNCOMPRESSED,SHA384,AesGcmKey(16))',
+     'java'),
+    # TODO(b/140101381) CC does not support Ecdsa with NIST_P384 and SHA384.
+    ('EcdsaPrivateKey(SHA384,NIST_P384,IEEE_P1363)', 'cc'),
+    ('EcdsaPrivateKey(SHA384,NIST_P384,IEEE_P1363)', 'python'),
+    ('EcdsaPrivateKey(SHA384,NIST_P384,DER)', 'cc'),
+    ('EcdsaPrivateKey(SHA384,NIST_P384,DER)', 'python'),
+]
+
+HASH_TYPES = [
+    common_pb2.UNKNOWN_HASH,
+    common_pb2.SHA1,
+    common_pb2.SHA256,
+    common_pb2.SHA384,
+    common_pb2.SHA512
+]
+
+CURVE_TYPES = [
+    common_pb2.UNKNOWN_CURVE,
+    common_pb2.NIST_P256,
+    common_pb2.NIST_P384,
+    common_pb2.NIST_P521,
+    common_pb2.CURVE25519
+]
+
+EC_POINT_FORMATS = [
+    common_pb2.UNKNOWN_FORMAT,
+    common_pb2.UNCOMPRESSED,
+    common_pb2.COMPRESSED,
+    common_pb2.DO_NOT_USE_CRUNCHY_UNCOMPRESSED
+]
+
+SIGNATURE_ENCODINGS = [
+    ecdsa_pb2.UNKNOWN_ENCODING,
+    ecdsa_pb2.IEEE_P1363,
+    ecdsa_pb2.DER
+]
+
+
+def aes_eax_test_cases():
+  for key_size in [15, 16, 24, 32, 64, 96]:
+    for iv_size in [11, 12, 16, 17, 24, 32]:
+      yield ('AesEaxKey(%d,%d)' % (key_size, iv_size),
+             aead.aead_key_templates.create_aes_eax_key_template(
+                 key_size, iv_size))
+
+
+def aes_gcm_test_cases():
+  for key_size in [15, 16, 24, 32, 64, 96]:
+    yield ('AesGcmKey(%d)' % key_size,
+           aead.aead_key_templates.create_aes_gcm_key_template(key_size))
+
+
+def aes_ctr_hmac_aead_test_cases():
+  def _test_case(aes_key_size=16, iv_size=16, hmac_key_size=16,
+                 tag_size=16, hash_type=common_pb2.SHA256):
+    return ('AesCtrHmacAeadKey(%d,%d,%d,%d,%s)' %
+            (aes_key_size, iv_size, hmac_key_size, tag_size,
+             common_pb2.HashType.Name(hash_type)),
+            aead.aead_key_templates.create_aes_ctr_hmac_aead_key_template(
+                aes_key_size=aes_key_size,
+                iv_size=iv_size,
+                hmac_key_size=hmac_key_size,
+                tag_size=tag_size,
+                hash_type=hash_type))
+  for aes_key_size in [15, 16, 24, 32, 64, 96]:
+    for iv_size in [11, 12, 16, 17, 24, 32]:
+      yield _test_case(aes_key_size=aes_key_size, iv_size=iv_size)
+  for hmac_key_size in [15, 16, 24, 32, 64, 96]:
+    for tag_size in [9, 10, 16, 20, 21, 24, 32, 33, 64, 65]:
+      for hash_type in HASH_TYPES:
+        yield _test_case(hmac_key_size=hmac_key_size, tag_size=tag_size,
+                         hash_type=hash_type)
+
+
+def hmac_test_cases():
+  def _test_case(key_size=32, tag_size=16, hash_type=common_pb2.SHA256):
+    return ('HmacKey(%d,%d,%s)' % (key_size, tag_size,
+                                   common_pb2.HashType.Name(hash_type)),
+            mac.mac_key_templates.create_hmac_key_template(
+                key_size, tag_size, hash_type))
+  for key_size in [15, 16, 24, 32, 64, 96]:
+    yield _test_case(key_size=key_size)
+  for tag_size in [9, 10, 16, 20, 21, 24, 32, 33, 64, 65]:
+    for hash_type in HASH_TYPES:
+      yield _test_case(tag_size=tag_size, hash_type=hash_type)
+
+
+def aes_siv_test_cases():
+  for key_size in [15, 16, 24, 32, 64, 96]:
+    yield ('AesSivKey(%d)' % key_size,
+           daead.deterministic_aead_key_templates.create_aes_siv_key_template(
+               key_size))
+
+
+def ecies_aead_hkdf_test_cases():
+  for curve_type in CURVE_TYPES:
+    for hash_type in HASH_TYPES:
+      ec_point_format = common_pb2.UNCOMPRESSED
+      dem_key_template = aead.aead_key_templates.AES128_GCM
+      yield ('EciesAeadHkdfPrivateKey(%s,%s,%s,AesGcmKey(16))' %
+             (common_pb2.EllipticCurveType.Name(curve_type),
+              common_pb2.EcPointFormat.Name(ec_point_format),
+              common_pb2.HashType.Name(hash_type)),
+             hybrid.hybrid_key_templates.create_ecies_aead_hkdf_key_template(
+                 curve_type, ec_point_format, hash_type, dem_key_template))
+  for ec_point_format in EC_POINT_FORMATS:
+    curve_type = common_pb2.NIST_P256
+    hash_type = common_pb2.SHA256
+    dem_key_template = aead.aead_key_templates.AES128_GCM
+    yield ('EciesAeadHkdfPrivateKey(%s,%s,%s,AesGcmKey(16))' %
+           (common_pb2.EllipticCurveType.Name(curve_type),
+            common_pb2.EcPointFormat.Name(ec_point_format),
+            common_pb2.HashType.Name(hash_type)),
+           hybrid.hybrid_key_templates.create_ecies_aead_hkdf_key_template(
+               curve_type, ec_point_format, hash_type, dem_key_template))
+  curve_type = common_pb2.NIST_P256
+  ec_point_format = common_pb2.UNCOMPRESSED
+  hash_type = common_pb2.SHA256
+  # Use invalid AEAD key template as DEM
+  # TODO(juerg): Once b/160130470 is fixed, increase test coverage to all
+  # aead templates.
+  dem_key_template = aead.aead_key_templates.create_aes_eax_key_template(15, 11)
+  yield ('EciesAeadHkdfPrivateKey(%s,%s,%s,AesEaxKey(15,11))' %
+         (common_pb2.EllipticCurveType.Name(curve_type),
+          common_pb2.EcPointFormat.Name(ec_point_format),
+          common_pb2.HashType.Name(hash_type)),
+         hybrid.hybrid_key_templates.create_ecies_aead_hkdf_key_template(
+             curve_type, ec_point_format, hash_type, dem_key_template))
+
+
+def ecdsa_test_cases():
+  for hash_type in HASH_TYPES:
+    for curve_type in CURVE_TYPES:
+      for signature_encoding in SIGNATURE_ENCODINGS:
+        yield ('EcdsaPrivateKey(%s,%s,%s)' %
+               (common_pb2.HashType.Name(hash_type),
+                common_pb2.EllipticCurveType.Name(curve_type),
+                ecdsa_pb2.EcdsaSignatureEncoding.Name(signature_encoding)),
+               signature.signature_key_templates.create_ecdsa_key_template(
+                   hash_type, curve_type, signature_encoding))
+
+
+def rsa_ssa_pkcs1_test_cases():
+  gen = signature.signature_key_templates.create_rsa_ssa_pkcs1_key_template
+  for hash_type in HASH_TYPES:
+    modulus_size = 2048
+    public_exponent = 65537
+    yield ('RsaSsaPkcs1PrivateKey(%s,%d,%d)' %
+           (common_pb2.HashType.Name(hash_type), modulus_size,
+            public_exponent),
+           gen(hash_type, modulus_size, public_exponent))
+  for modulus_size in [0, 2000, 3072, 4096]:
+    hash_type = common_pb2.SHA256
+    public_exponent = 65537
+    yield ('RsaSsaPkcs1PrivateKey(%s,%d,%d)' %
+           (common_pb2.HashType.Name(hash_type), modulus_size,
+            public_exponent),
+           gen(hash_type, modulus_size, public_exponent))
+  # TODO(b/160214390): Add tests for public_exponent, once this bug is resolved.
+
+
+def rsa_ssa_pss_test_cases():
+  gen = signature.signature_key_templates.create_rsa_ssa_pss_key_template
+  for hash_type in HASH_TYPES:
+    salt_length = 32
+    modulus_size = 2048
+    public_exponent = 65537
+    yield ('RsaSsaPssPrivateKey(%s,%s,%d,%d,%d)' %
+           (common_pb2.HashType.Name(hash_type),
+            common_pb2.HashType.Name(hash_type), salt_length, modulus_size,
+            public_exponent),
+           gen(hash_type, hash_type, salt_length, modulus_size,
+               public_exponent))
+  for salt_length in [-3, 0, 1, 16, 64]:
+    hash_type = common_pb2.SHA256
+    modulus_size = 2048
+    public_exponent = 65537
+    yield ('RsaSsaPssPrivateKey(%s,%s,%d,%d,%d)' %
+           (common_pb2.HashType.Name(hash_type),
+            common_pb2.HashType.Name(hash_type), salt_length, modulus_size,
+            public_exponent),
+           gen(hash_type, hash_type, salt_length, modulus_size,
+               public_exponent))
+  for modulus_size in [0, 2000, 3072, 4096]:
+    hash_type = common_pb2.SHA256
+    salt_length = 32
+    public_exponent = 65537
+    yield ('RsaSsaPssPrivateKey(%s,%s,%d,%d,%d)' %
+           (common_pb2.HashType.Name(hash_type),
+            common_pb2.HashType.Name(hash_type), salt_length, modulus_size,
+            public_exponent),
+           gen(hash_type, hash_type, salt_length, modulus_size,
+               public_exponent))
+  hash_type1 = common_pb2.SHA256
+  hash_type2 = common_pb2.SHA512
+  salt_length = 32
+  modulus_size = 2048
+  public_exponent = 65537
+  yield ('RsaSsaPssPrivateKey(%s,%s,%d,%d,%d)' %
+         (common_pb2.HashType.Name(hash_type1),
+          common_pb2.HashType.Name(hash_type2), salt_length, modulus_size,
+          public_exponent),
+         gen(hash_type1, hash_type2, salt_length, modulus_size,
+             public_exponent))
+
+
+def setUpModule():
+  aead.register()
+  daead.register()
+  mac.register()
+  hybrid.register()
+  signature.register()
+  testing_servers.start()
+
+
+def tearDownModule():
+  testing_servers.stop()
+
+
+class KeyGenerationConsistencyTest(parameterized.TestCase):
+
+  @parameterized.parameters(
+      itertools.chain(aes_eax_test_cases(),
+                      aes_gcm_test_cases(),
+                      aes_ctr_hmac_aead_test_cases(),
+                      hmac_test_cases(),
+                      aes_siv_test_cases(),
+                      ecies_aead_hkdf_test_cases(),
+                      ecdsa_test_cases(),
+                      rsa_ssa_pkcs1_test_cases(),
+                      rsa_ssa_pss_test_cases()))
+  def test_key_generation_consistency(self, name, template):
+    supported_langs = TYPE_URL_TO_SUPPORTED_LANGUAGES[template.type_url]
+    failures = 0
+    results = {}
+    for lang in supported_langs:
+      try:
+        _ = testing_servers.new_keyset(lang, template)
+        if (name, lang) in SUCCEEDS_BUT_SHOULD_FAIL:
+          failures += 1
+        if (name, lang) in FAILS_BUT_SHOULD_SUCCEED:
+          self.fail('(%s, %s) succeeded, but is in FAILS_BUT_SHOULD_SUCCEED' %
+                    (name, lang))
+        results[lang] = 'success'
+      except tink.TinkError as e:
+        if (name, lang) not in FAILS_BUT_SHOULD_SUCCEED:
+          failures += 1
+        if (name, lang) in SUCCEEDS_BUT_SHOULD_FAIL:
+          self.fail(
+              '(%s, %s) is in SUCCEEDS_BUT_SHOULD_FAIL, but failed with %s' %
+              (name, lang, e))
+        results[lang] = e
+    # Test that either all supported langs accept the template, or all reject.
+    if failures not in [0, len(supported_langs)]:
+      self.fail('key generation for template %s is inconsistent: %s' %
+                (name, results))
+
+
+if __name__ == '__main__':
+  absltest.main()
diff --git a/tools/testing/cross_language/mac_test.py b/testing/cross_language/mac_test.py
similarity index 79%
rename from tools/testing/cross_language/mac_test.py
rename to testing/cross_language/mac_test.py
index 8a38b43..785cd53 100644
--- a/tools/testing/cross_language/mac_test.py
+++ b/testing/cross_language/mac_test.py
@@ -17,13 +17,19 @@
 import tink
 from tink import mac
 
-from tools.testing import supported_key_types
-from tools.testing.cross_language.util import cli_mac
-from tools.testing.cross_language.util import keyset_manager
+from util import supported_key_types
+from util import testing_servers
+
+SUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE['mac']
 
 
 def setUpModule():
   mac.register()
+  testing_servers.start()
+
+
+def tearDownModule():
+  testing_servers.stop()
 
 
 class MacTest(parameterized.TestCase):
@@ -32,14 +38,13 @@
       supported_key_types.test_cases(supported_key_types.MAC_KEY_TYPES))
   def test_encrypt_decrypt(self, key_template_name, supported_langs):
     key_template = supported_key_types.KEY_TEMPLATE[key_template_name]
-    keyset_handle = keyset_manager.new_keyset_handle(key_template)
+    keyset = testing_servers.new_keyset('java', key_template)
     supported_macs = [
-        cli_mac.CliMac(lang, keyset_handle)
-        for lang in supported_langs
+        testing_servers.mac(lang, keyset) for lang in supported_langs
     ]
     unsupported_macs = [
-        cli_mac.CliMac(lang, keyset_handle)
-        for lang in cli_mac.LANGUAGES
+        testing_servers.mac(lang, keyset)
+        for lang in SUPPORTED_LANGUAGES
         if lang not in supported_langs
     ]
     for p in supported_macs:
diff --git a/testing/cross_language/requirements.txt b/testing/cross_language/requirements.txt
new file mode 100644
index 0000000..b998a06
--- /dev/null
+++ b/testing/cross_language/requirements.txt
@@ -0,0 +1 @@
+absl-py
diff --git a/tools/testing/cross_language/signature_test.py b/testing/cross_language/signature_test.py
similarity index 72%
rename from tools/testing/cross_language/signature_test.py
rename to testing/cross_language/signature_test.py
index c01e502..3c508e8 100644
--- a/tools/testing/cross_language/signature_test.py
+++ b/testing/cross_language/signature_test.py
@@ -17,13 +17,20 @@
 import tink
 from tink import signature
 
-from tools.testing import supported_key_types
-from tools.testing.cross_language.util import cli_signature
-from tools.testing.cross_language.util import keyset_manager
+from util import supported_key_types
+from util import testing_servers
+
+SUPPORTED_LANGUAGES = (testing_servers
+                       .SUPPORTED_LANGUAGES_BY_PRIMITIVE['signature'])
 
 
 def setUpModule():
   signature.register()
+  testing_servers.start()
+
+
+def tearDownModule():
+  testing_servers.stop()
 
 
 class SignaturePythonTest(parameterized.TestCase):
@@ -32,24 +39,24 @@
       supported_key_types.test_cases(supported_key_types.SIGNATURE_KEY_TYPES))
   def test_encrypt_decrypt(self, key_template_name, supported_langs):
     key_template = supported_key_types.KEY_TEMPLATE[key_template_name]
-    private_handle = keyset_manager.new_keyset_handle(key_template)
+    private_keyset = testing_servers.new_keyset('java', key_template)
     supported_signers = [
-        cli_signature.CliPublicKeySign(lang, private_handle)
+        testing_servers.public_key_sign(lang, private_keyset)
         for lang in supported_langs
     ]
     unsupported_signers = [
-        cli_signature.CliPublicKeySign(lang, private_handle)
-        for lang in cli_signature.LANGUAGES
+        testing_servers.public_key_sign(lang, private_keyset)
+        for lang in SUPPORTED_LANGUAGES
         if lang not in supported_langs
     ]
-    public_handle = private_handle.public_keyset_handle()
+    public_keyset = testing_servers.public_keyset('java', private_keyset)
     supported_verifiers = [
-        cli_signature.CliPublicKeyVerify(lang, public_handle)
+        testing_servers.public_key_verify(lang, public_keyset)
         for lang in supported_langs
     ]
     unsupported_verifiers = [
-        cli_signature.CliPublicKeyVerify(lang, public_handle)
-        for lang in cli_signature.LANGUAGES
+        testing_servers.public_key_verify(lang, public_keyset)
+        for lang in testing_servers.LANGUAGES
         if lang not in supported_langs
     ]
     for signer in supported_signers:
diff --git a/testing/cross_language/streaming_aead_test.py b/testing/cross_language/streaming_aead_test.py
new file mode 100644
index 0000000..befe0ba
--- /dev/null
+++ b/testing/cross_language/streaming_aead_test.py
@@ -0,0 +1,84 @@
+# 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.
+"""Cross-language tests for the StreamingAead primitive."""
+
+import io
+
+from absl.testing import absltest
+from absl.testing import parameterized
+
+import tink
+from tink import streaming_aead
+
+from util import supported_key_types
+from util import testing_servers
+
+SUPPORTED_LANGUAGES = (testing_servers
+                       .SUPPORTED_LANGUAGES_BY_PRIMITIVE['streaming_aead'])
+
+
+def setUpModule():
+  streaming_aead.register()
+  testing_servers.start()
+
+
+def tearDownModule():
+  testing_servers.stop()
+
+
+class StreamingAeadPythonTest(parameterized.TestCase):
+
+  @parameterized.parameters(
+      supported_key_types.test_cases(
+          supported_key_types.STREAMING_AEAD_KEY_TYPES))
+  def test_encrypt_decrypt(self, key_template_name, supported_langs):
+    key_template = supported_key_types.KEY_TEMPLATE[key_template_name]
+    # Use java to generate keys, as it supports all key types.
+    keyset = testing_servers.new_keyset('java', key_template)
+    supported_streaming_aeads = [
+        testing_servers.streaming_aead(lang, keyset) for lang in supported_langs
+    ]
+    unsupported_streaming_aeads = [
+        testing_servers.streaming_aead(lang, keyset)
+        for lang in SUPPORTED_LANGUAGES
+        if lang not in supported_langs
+    ]
+    for p in supported_streaming_aeads:
+      plaintext = (
+          b'This is some plaintext message to be encrypted using key_template '
+          b'%s using %s for encryption.'
+          % (key_template_name.encode('utf8'), p.lang.encode('utf8')))
+      associated_data = (
+          b'Some associated data for %s using %s for encryption.' %
+          (key_template_name.encode('utf8'), p.lang.encode('utf8')))
+      plaintext_stream = io.BytesIO(plaintext)
+      ciphertext_result_stream = p.new_encrypting_stream(
+          plaintext_stream, associated_data)
+      ciphertext = ciphertext_result_stream.read()
+      for p2 in supported_streaming_aeads:
+        ciphertext_stream = io.BytesIO(ciphertext)
+        decrypted_stream = p2.new_decrypting_stream(
+            ciphertext_stream, associated_data)
+        self.assertEqual(decrypted_stream.read(), plaintext)
+      for p2 in unsupported_streaming_aeads:
+        with self.assertRaises(tink.TinkError):
+          ciphertext_stream = io.BytesIO(ciphertext)
+          decrypted_stream = p2.new_decrypting_stream(
+              ciphertext_stream, associated_data)
+    for p in unsupported_streaming_aeads:
+      with self.assertRaises(tink.TinkError):
+        plaintext_stream = io.BytesIO(b'plaintext')
+        ciphertext_result_stream = p.new_encrypting_stream(
+            plaintext_stream, b'associated_data')
+
+if __name__ == '__main__':
+  absltest.main()
diff --git a/testing/cross_language/util/BUILD.bazel b/testing/cross_language/util/BUILD.bazel
new file mode 100644
index 0000000..a83cd24
--- /dev/null
+++ b/testing/cross_language/util/BUILD.bazel
@@ -0,0 +1,127 @@
+load("@rules_proto_grpc//python:defs.bzl", "python_grpc_library")
+load("@rules_python//python:defs.bzl", "py_library")
+load("@pip_deps//:requirements.bzl", "requirement")
+
+package(
+    default_testonly = 1,
+    default_visibility = ["//visibility:public"],
+)
+
+licenses(["notice"])
+
+python_grpc_library(
+    name = "testing_api_python_library",
+    deps = ["@tink_base//proto/testing:testing_api_proto"],
+)
+
+py_library(
+    name = "supported_key_types",
+    testonly = 1,
+    srcs = ["supported_key_types.py"],
+    deps = [
+        "@tink_py//tink/aead",
+        "@tink_py//tink/daead",
+        "@tink_py//tink/hybrid",
+        "@tink_py//tink/mac",
+        "@tink_py//tink/proto:tink_py_pb2",
+        "@tink_py//tink/signature",
+        "@tink_py//tink/streaming_aead",
+    ],
+)
+
+py_test(
+    name = "supported_key_types_test",
+    srcs = ["supported_key_types_test.py"],
+    python_version = "PY3",
+    srcs_version = "PY3",
+    deps = [
+        ":supported_key_types",
+        requirement("absl-py"),
+    ],
+)
+
+py_library(
+    name = "_primitives",
+    srcs = ["_primitives.py"],
+    srcs_version = "PY3",
+    deps = [
+        ":testing_api_python_library",
+        "@com_google_protobuf//:protobuf_python",
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/aead",
+        "@tink_py//tink/daead",
+        "@tink_py//tink/hybrid",
+        "@tink_py//tink/mac",
+        "@tink_py//tink/signature",
+        "@tink_py//tink/streaming_aead",
+        requirement("absl-py"),
+    ],
+)
+
+py_library(
+    name = "testing_servers",
+    srcs = ["testing_servers.py"],
+    srcs_version = "PY3",
+    deps = [
+        ":_primitives",
+        ":testing_api_python_library",
+        "@com_google_protobuf//:protobuf_python",
+        "@tink_py//tink:cleartext_keyset_handle",
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/aead",
+        "@tink_py//tink/daead",
+        "@tink_py//tink/hybrid",
+        "@tink_py//tink/mac",
+        "@tink_py//tink/proto:tink_py_pb2",
+        "@tink_py//tink/signature",
+        "@tink_py//tink/streaming_aead",
+        requirement("absl-py"),
+        "@org_python_pypi_portpicker//:portpicker",
+    ],
+)
+
+py_test(
+    name = "testing_servers_test",
+    srcs = ["testing_servers_test.py"],
+    data = [
+        ":testing_servers",
+    ],
+    python_version = "PY3",
+    srcs_version = "PY3",
+    deps = [
+        ":testing_api_python_library",
+        requirement("absl-py"),
+        "@org_python_pypi_portpicker//:portpicker",
+        "@tink_py//tink/aead",
+        "@tink_py//tink/daead",
+        "@tink_py//tink/hybrid",
+        "@tink_py//tink/mac",
+        "@tink_py//tink/signature",
+        "@tink_py//tink/streaming_aead",
+    ],
+)
+
+py_library(
+    name = "keyset_builder",
+    testonly = 1,
+    srcs = [
+        "keyset_builder.py",
+    ],
+    deps = [
+        "@tink_py//tink:cleartext_keyset_handle",
+        "@tink_py//tink:tink_python",
+    ],
+)
+
+py_test(
+    name = "keyset_builder_test",
+    srcs = ["keyset_builder_test.py"],
+    python_version = "PY3",
+    srcs_version = "PY3",
+    deps = [
+        ":keyset_builder",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/aead",
+    ],
+)
diff --git a/testing/cross_language/util/_primitives.py b/testing/cross_language/util/_primitives.py
new file mode 100644
index 0000000..933206d
--- /dev/null
+++ b/testing/cross_language/util/_primitives.py
@@ -0,0 +1,292 @@
+# 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.
+"""Implements tink primitives from gRPC testing_api stubs."""
+
+from __future__ import absolute_import
+from __future__ import division
+# Placeholder for import for type annotations
+from __future__ import print_function
+
+import io
+from typing import BinaryIO, Text
+from absl import logging
+
+import tink
+from tink import aead
+from tink import daead
+from tink import hybrid
+from tink import mac
+from tink import signature as tink_signature
+from tink import streaming_aead
+
+from tink.proto import tink_pb2
+from proto.testing import testing_api_pb2
+from proto.testing import testing_api_pb2_grpc
+
+
+def new_keyset(stub: testing_api_pb2_grpc.KeysetStub,
+               key_template: tink_pb2.KeyTemplate) -> bytes:
+  gen_request = testing_api_pb2.KeysetGenerateRequest(
+      template=key_template.SerializeToString())
+  gen_response = stub.Generate(gen_request)
+  if gen_response.err:
+    raise tink.TinkError(gen_response.err)
+  return gen_response.keyset
+
+
+def public_keyset(stub: testing_api_pb2_grpc.KeysetStub,
+                  private_keyset: bytes) -> bytes:
+  request = testing_api_pb2.KeysetPublicRequest(private_keyset=private_keyset)
+  response = stub.Public(request)
+  if response.err:
+    raise tink.TinkError(response.err)
+  return response.public_keyset
+
+
+def keyset_to_json(
+    stub: testing_api_pb2_grpc.KeysetStub,
+    keyset: bytes) -> Text:
+  request = testing_api_pb2.KeysetToJsonRequest(keyset=keyset)
+  response = stub.ToJson(request)
+  if response.err:
+    raise tink.TinkError(response.err)
+  return response.json_keyset
+
+
+def keyset_from_json(
+    stub: testing_api_pb2_grpc.KeysetStub,
+    json_keyset: Text) -> bytes:
+  request = testing_api_pb2.KeysetFromJsonRequest(json_keyset=json_keyset)
+  response = stub.FromJson(request)
+  if response.err:
+    raise tink.TinkError(response.err)
+  return response.keyset
+
+
+class Aead(aead.Aead):
+  """Wraps AEAD service stub into an Aead primitive."""
+
+  def __init__(self, lang: Text, stub: testing_api_pb2_grpc.AeadStub,
+               keyset: bytes) -> None:
+    self.lang = lang
+    self._stub = stub
+    self._keyset = keyset
+
+  def encrypt(self, plaintext: bytes, associated_data: bytes) -> bytes:
+    logging.info('encrypt in lang %s.', self.lang)
+    enc_request = testing_api_pb2.AeadEncryptRequest(
+        keyset=self._keyset,
+        plaintext=plaintext,
+        associated_data=associated_data)
+    enc_response = self._stub.Encrypt(enc_request)
+    if enc_response.err:
+      logging.info('error encrypt in %s: %s', self.lang, enc_response.err)
+      raise tink.TinkError(enc_response.err)
+    return enc_response.ciphertext
+
+  def decrypt(self, ciphertext: bytes, associated_data: bytes) -> bytes:
+    logging.info('decrypt in lang %s.', self.lang)
+    dec_request = testing_api_pb2.AeadDecryptRequest(
+        keyset=self._keyset,
+        ciphertext=ciphertext,
+        associated_data=associated_data)
+    dec_response = self._stub.Decrypt(dec_request)
+    if dec_response.err:
+      logging.info('error decrypt in %s: %s', self.lang, dec_response.err)
+      raise tink.TinkError(dec_response.err)
+    return dec_response.plaintext
+
+
+class DeterministicAead(daead.DeterministicAead):
+  """Wraps DAEAD services stub into an DeterministicAead primitive."""
+
+  def __init__(self, lang: Text,
+               stub: testing_api_pb2_grpc.DeterministicAeadStub,
+               keyset: bytes) -> None:
+    self.lang = lang
+    self._stub = stub
+    self._keyset = keyset
+
+  def encrypt_deterministically(self, plaintext: bytes,
+                                associated_data: bytes) -> bytes:
+    """Encrypts."""
+    logging.info('encrypt in lang %s.', self.lang)
+    enc_request = testing_api_pb2.DeterministicAeadEncryptRequest(
+        keyset=self._keyset,
+        plaintext=plaintext,
+        associated_data=associated_data)
+    enc_response = self._stub.EncryptDeterministically(enc_request)
+    if enc_response.err:
+      logging.info('error encrypt in %s: %s', self.lang, enc_response.err)
+      raise tink.TinkError(enc_response.err)
+    return enc_response.ciphertext
+
+  def decrypt_deterministically(self, ciphertext: bytes,
+                                associated_data: bytes) -> bytes:
+    """Decrypts."""
+    logging.info('decrypt in lang %s.', self.lang)
+    dec_request = testing_api_pb2.DeterministicAeadDecryptRequest(
+        keyset=self._keyset,
+        ciphertext=ciphertext,
+        associated_data=associated_data)
+    dec_response = self._stub.DecryptDeterministically(dec_request)
+    if dec_response.err:
+      logging.info('error decrypt in %s: %s', self.lang, dec_response.err)
+      raise tink.TinkError(dec_response.err)
+    return dec_response.plaintext
+
+
+class StreamingAead(streaming_aead.StreamingAead):
+  """Wraps Streaming AEAD service stub into a StreamingAead primitive."""
+
+  def __init__(self, lang: Text, stub: testing_api_pb2_grpc.StreamingAeadStub,
+               keyset: bytes) -> None:
+    self.lang = lang
+    self._stub = stub
+    self._keyset = keyset
+
+  def new_encrypting_stream(self, plaintext: BinaryIO,
+                            associated_data: bytes) -> BinaryIO:
+    logging.info('encrypt in lang %s.', self.lang)
+    enc_request = testing_api_pb2.StreamingAeadEncryptRequest(
+        keyset=self._keyset,
+        plaintext=plaintext.read(),
+        associated_data=associated_data)
+    enc_response = self._stub.Encrypt(enc_request)
+    if enc_response.err:
+      logging.info('error encrypt in %s: %s', self.lang, enc_response.err)
+      raise tink.TinkError(enc_response.err)
+    return io.BytesIO(enc_response.ciphertext)
+
+  def new_decrypting_stream(self, ciphertext: BinaryIO,
+                            associated_data: bytes) -> BinaryIO:
+    logging.info('decrypt in lang %s.', self.lang)
+    dec_request = testing_api_pb2.StreamingAeadDecryptRequest(
+        keyset=self._keyset,
+        ciphertext=ciphertext.read(),
+        associated_data=associated_data)
+    dec_response = self._stub.Decrypt(dec_request)
+    if dec_response.err:
+      logging.info('error decrypt in %s: %s', self.lang, dec_response.err)
+      raise tink.TinkError(dec_response.err)
+    return io.BytesIO(dec_response.plaintext)
+
+
+class Mac(mac.Mac):
+  """Wraps MAC service stub into an Mac primitive."""
+
+  def __init__(self, lang: Text, stub: testing_api_pb2_grpc.MacStub,
+               keyset: bytes) -> None:
+    self.lang = lang
+    self._stub = stub
+    self._keyset = keyset
+
+  def compute_mac(self, data: bytes) -> bytes:
+    logging.info('compute_mac in lang %s.', self.lang)
+    request = testing_api_pb2.ComputeMacRequest(keyset=self._keyset, data=data)
+    response = self._stub.ComputeMac(request)
+    if response.err:
+      logging.info('error compute_mac in %s: %s', self.lang, response.err)
+      raise tink.TinkError(response.err)
+    return response.mac_value
+
+  def verify_mac(self, mac_value: bytes, data: bytes) -> None:
+    logging.info('verify_mac in lang %s.', self.lang)
+    request = testing_api_pb2.VerifyMacRequest(
+        keyset=self._keyset, mac_value=mac_value, data=data)
+    response = self._stub.VerifyMac(request)
+    if response.err:
+      logging.info('error verify_mac in %s: %s', self.lang, response.err)
+      raise tink.TinkError(response.err)
+
+
+class HybridEncrypt(hybrid.HybridEncrypt):
+  """Implements the HybridEncrypt primitive using a hybrid service stub."""
+
+  def __init__(self, lang: Text, stub: testing_api_pb2_grpc.HybridStub,
+               public_handle: bytes) -> None:
+    self.lang = lang
+    self._stub = stub
+    self._public_handle = public_handle
+
+  def encrypt(self, plaintext: bytes, context_info: bytes) -> bytes:
+    logging.info('hybrid Sencrypt in lang %s.', self.lang)
+    enc_request = testing_api_pb2.HybridEncryptRequest(
+        public_keyset=self._public_handle,
+        plaintext=plaintext,
+        context_info=context_info)
+    enc_response = self._stub.Encrypt(enc_request)
+    if enc_response.err:
+      logging.info('error encrypt in %s: %s', self.lang, enc_response.err)
+      raise tink.TinkError(enc_response.err)
+    return enc_response.ciphertext
+
+
+class HybridDecrypt(hybrid.HybridDecrypt):
+  """Implements the HybridDecrypt primitive using a hybrid service stub."""
+
+  def __init__(self, lang: Text, stub: testing_api_pb2_grpc.HybridStub,
+               private_handle: bytes) -> None:
+    self.lang = lang
+    self._stub = stub
+    self._private_handle = private_handle
+
+  def decrypt(self, ciphertext: bytes, context_info: bytes) -> bytes:
+    logging.info('decrypt in lang %s.', self.lang)
+    dec_request = testing_api_pb2.HybridDecryptRequest(
+        private_keyset=self._private_handle,
+        ciphertext=ciphertext,
+        context_info=context_info)
+    dec_response = self._stub.Decrypt(dec_request)
+    if dec_response.err:
+      logging.info('error hybriddecrypt in %s: %s', self.lang, dec_response.err)
+      raise tink.TinkError(dec_response.err)
+    return dec_response.plaintext
+
+
+class PublicKeySign(tink_signature.PublicKeySign):
+  """Implements the PublicKeySign primitive using a signature service stub."""
+
+  def __init__(self, lang: Text, stub: testing_api_pb2_grpc.SignatureStub,
+               private_handle: bytes) -> None:
+    self.lang = lang
+    self._stub = stub
+    self._private_handle = private_handle
+
+  def sign(self, data: bytes) -> bytes:
+    logging.info('compute_mac in lang %s.', self.lang)
+    request = testing_api_pb2.SignatureSignRequest(
+        private_keyset=self._private_handle, data=data)
+    response = self._stub.Sign(request)
+    if response.err:
+      logging.info('error signature sign in %s: %s', self.lang, response.err)
+      raise tink.TinkError(response.err)
+    return response.signature
+
+
+class PublicKeyVerify(tink_signature.PublicKeyVerify):
+  """Implements the PublicKeyVerify primitive using a signature service stub."""
+
+  def __init__(self, lang: Text, stub: testing_api_pb2_grpc.SignatureStub,
+               public_handle: bytes) -> None:
+    self.lang = lang
+    self._stub = stub
+    self._public_handle = public_handle
+
+  def verify(self, signature: bytes, data: bytes) -> None:
+    logging.info('signature verify in lang %s.', self.lang)
+    request = testing_api_pb2.SignatureVerifyRequest(
+        public_keyset=self._public_handle, signature=signature, data=data)
+    response = self._stub.Verify(request)
+    if response.err:
+      logging.info('error signature verify in %s: %s', self.lang, response.err)
+      raise tink.TinkError(response.err)
diff --git a/testing/cross_language/util/keyset_builder.py b/testing/cross_language/util/keyset_builder.py
new file mode 100644
index 0000000..1993d20
--- /dev/null
+++ b/testing/cross_language/util/keyset_builder.py
@@ -0,0 +1,113 @@
+# 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.
+"""Python implementation of a KeysetBuilder."""
+
+# Placeholder for import for type annotations
+
+import io
+import random
+import tink
+from tink import cleartext_keyset_handle
+from tink.proto import tink_pb2
+
+_MAX_INT32 = 4294967295  # 2^32-1
+
+
+def _new_key_data(key_template: tink_pb2.KeyTemplate) -> tink_pb2.KeyData:
+  return tink.core.Registry.new_key_data(key_template)
+
+
+def _generate_unused_key_id(keyset: tink_pb2.Keyset) -> int:
+  while True:
+    key_id = random.randint(1, _MAX_INT32)
+    if key_id not in {key.key_id for key in keyset.key}:
+      return key_id
+
+
+class KeysetBuilder(object):
+  """A KeysetBuilder provides convenience methods for managing Keysets.
+
+  It provides methods for adding, disabling, enabling, or deleting keys.
+  The validity of the keyset is checked when creating a keyset_handle.
+  """
+
+  def __init__(self, keyset_proto: tink_pb2.Keyset):
+    self._keyset = keyset_proto
+
+  def keyset_handle(self) -> tink.KeysetHandle:
+    keyset_copy = tink_pb2.Keyset()
+    keyset_copy.CopyFrom(self._keyset)
+    return cleartext_keyset_handle.from_keyset(keyset_copy)
+
+  def keyset(self) -> bytes:
+    return self._keyset.SerializeToString()
+
+  def add_new_key(self, key_template: tink_pb2.KeyTemplate) -> int:
+    """Generates a new key, adds it to the keyset, and returns its ID."""
+    new_key = self._keyset.key.add()
+    new_key.key_data.CopyFrom(_new_key_data(key_template))
+    new_key.status = tink_pb2.ENABLED
+    new_key_id = _generate_unused_key_id(self._keyset)
+    new_key.key_id = new_key_id
+    new_key.output_prefix_type = key_template.output_prefix_type
+    return new_key_id
+
+  def set_primary_key(self, key_id: int) -> None:
+    """Sets a key as primary."""
+    for key in self._keyset.key:
+      if key.key_id == key_id:
+        self._keyset.primary_key_id = key_id
+        return
+    raise tink.TinkError('key not found: %d' % key_id)
+
+  def enable_key(self, key_id: int) -> None:
+    """Enables a key."""
+    for key in self._keyset.key:
+      if key.key_id == key_id:
+        key.status = tink_pb2.ENABLED
+        return
+    raise tink.TinkError('key not found: %d' % key_id)
+
+  def disable_key(self, key_id: int) -> None:
+    """Disables a key."""
+    for key in self._keyset.key:
+      if key.key_id == key_id:
+        key.status = tink_pb2.DISABLED
+        return
+    raise tink.TinkError('key not found: %d' % key_id)
+
+  def delete_key(self, key_id: int) -> None:
+    """Deletes a key."""
+    for key in self._keyset.key:
+      if key.key_id == key_id:
+        self._keyset.key.remove(key)
+        return
+    raise tink.TinkError('key not found: %d' % key_id)
+
+
+def from_keyset(keyset: bytes) -> KeysetBuilder:
+  """Return a KeysetBuilder for a Keyset copied from a KeysetHandle."""
+  keyset_proto = tink_pb2.Keyset()
+  keyset_proto.ParseFromString(keyset)
+  return KeysetBuilder(keyset_proto)
+
+
+def from_keyset_handle(keyset_handle: tink.KeysetHandle) -> KeysetBuilder:
+  """Return a KeysetBuilder for a Keyset copied from a KeysetHandle."""
+  keyset_buffer = io.BytesIO()
+  cleartext_keyset_handle.write(
+      tink.BinaryKeysetWriter(keyset_buffer), keyset_handle)
+  return from_keyset(keyset_buffer.getvalue())
+
+
+def new_keyset_builder() -> KeysetBuilder:
+  return KeysetBuilder(tink_pb2.Keyset())
diff --git a/testing/cross_language/util/keyset_builder_test.py b/testing/cross_language/util/keyset_builder_test.py
new file mode 100644
index 0000000..26a0723
--- /dev/null
+++ b/testing/cross_language/util/keyset_builder_test.py
@@ -0,0 +1,132 @@
+# 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 tink.testing.cross_language.util.keyset_builder."""
+
+from absl.testing import absltest
+# from absl.testing import parameterized
+
+import tink
+from tink import aead
+
+from util import keyset_builder
+
+
+def setUpModule():
+  aead.register()
+
+
+class KeysetBuilderTest(absltest.TestCase):
+
+  def test_keyset_handle_conversion(self):
+    keyset_handle1 = tink.new_keyset_handle(aead.aead_key_templates.AES128_GCM)
+    p1 = keyset_handle1.primitive(aead.Aead)
+    builder = keyset_builder.from_keyset_handle(keyset_handle1)
+    keyset_handle2 = builder.keyset_handle()
+    p2 = keyset_handle2.primitive(aead.Aead)
+    ciphertext = p1.encrypt(b'plaintext', b'ad')
+    self.assertEqual(p2.decrypt(ciphertext, b'ad'), b'plaintext')
+
+  def test_keyset_conversion(self):
+    builder1 = keyset_builder.new_keyset_builder()
+    new_key_id = builder1.add_new_key(aead.aead_key_templates.AES128_GCM)
+    builder1.set_primary_key(new_key_id)
+    keyset = builder1.keyset()
+    keyset_handle1 = builder1.keyset_handle()
+    p1 = keyset_handle1.primitive(aead.Aead)
+    builder2 = keyset_builder.from_keyset(keyset)
+    keyset_handle2 = builder2.keyset_handle()
+    p2 = keyset_handle2.primitive(aead.Aead)
+    ciphertext = p1.encrypt(b'plaintext', b'ad')
+    self.assertEqual(p2.decrypt(ciphertext, b'ad'), b'plaintext')
+
+  def test_add_new_key_new_id(self):
+    builder = keyset_builder.new_keyset_builder()
+    key_id1 = builder.add_new_key(aead.aead_key_templates.AES128_GCM)
+    key_id2 = builder.add_new_key(aead.aead_key_templates.AES128_GCM)
+    self.assertNotEqual(key_id1, key_id2)
+
+  def test_set_primary_success(self):
+    builder = keyset_builder.new_keyset_builder()
+    secondary_key_id = builder.add_new_key(aead.aead_key_templates.AES128_GCM)
+    builder.set_primary_key(secondary_key_id)
+
+  def test_operation_on_unknown_key_fails(self):
+    builder = keyset_builder.new_keyset_builder()
+    key_id = builder.add_new_key(
+        aead.aead_key_templates.AES128_GCM)
+    unknown_key_id = key_id + 1
+    with self.assertRaises(tink.TinkError):
+      builder.set_primary_key(unknown_key_id)
+    with self.assertRaises(tink.TinkError):
+      builder.enable_key(unknown_key_id)
+    with self.assertRaises(tink.TinkError):
+      builder.disable_key(unknown_key_id)
+    with self.assertRaises(tink.TinkError):
+      builder.delete_key(unknown_key_id)
+
+  def test_key_rotation(self):
+    builder = keyset_builder.new_keyset_builder()
+    older_key_id = builder.add_new_key(aead.aead_key_templates.AES128_GCM)
+    builder.set_primary_key(older_key_id)
+    p1 = builder.keyset_handle().primitive(aead.Aead)
+
+    newer_key_id = builder.add_new_key(aead.aead_key_templates.AES128_GCM)
+    p2 = builder.keyset_handle().primitive(aead.Aead)
+
+    builder.set_primary_key(newer_key_id)
+    p3 = builder.keyset_handle().primitive(aead.Aead)
+
+    builder.disable_key(older_key_id)
+    p4 = builder.keyset_handle().primitive(aead.Aead)
+
+    builder.delete_key(older_key_id)
+
+    self.assertNotEqual(older_key_id, newer_key_id)
+    # p1 encrypts with the older key. So p1, p2 and p3 can decrypt it,
+    # but not p4.
+    ciphertext1 = p1.encrypt(b'plaintext', b'ad')
+    self.assertEqual(p1.decrypt(ciphertext1, b'ad'), b'plaintext')
+    self.assertEqual(p2.decrypt(ciphertext1, b'ad'), b'plaintext')
+    self.assertEqual(p3.decrypt(ciphertext1, b'ad'), b'plaintext')
+    with self.assertRaises(tink.TinkError):
+      _ = p4.decrypt(ciphertext1, b'ad')
+
+    # p2 encrypts with the older key. So p1, p2 and p3 can decrypt it,
+    # but not p4.
+    ciphertext2 = p2.encrypt(b'plaintext', b'ad')
+    self.assertEqual(p1.decrypt(ciphertext2, b'ad'), b'plaintext')
+    self.assertEqual(p2.decrypt(ciphertext2, b'ad'), b'plaintext')
+    self.assertEqual(p3.decrypt(ciphertext2, b'ad'), b'plaintext')
+    with self.assertRaises(tink.TinkError):
+      _ = p4.decrypt(ciphertext2, b'ad')
+
+    # p3 encrypts with the newer key. So p2, p3 and p4 can decrypt it,
+    # but not p1.
+    ciphertext3 = p3.encrypt(b'plaintext', b'ad')
+    with self.assertRaises(tink.TinkError):
+      _ = p1.decrypt(ciphertext3, b'ad')
+    self.assertEqual(p2.decrypt(ciphertext3, b'ad'), b'plaintext')
+    self.assertEqual(p3.decrypt(ciphertext3, b'ad'), b'plaintext')
+    self.assertEqual(p4.decrypt(ciphertext3, b'ad'), b'plaintext')
+
+    # p4 encrypts with the newer key. So p2, p3 and p4 can decrypt it,
+    # but not p1.
+    ciphertext4 = p3.encrypt(b'plaintext', b'ad')
+    with self.assertRaises(tink.TinkError):
+      _ = p1.decrypt(ciphertext4, b'ad')
+    self.assertEqual(p2.decrypt(ciphertext4, b'ad'), b'plaintext')
+    self.assertEqual(p3.decrypt(ciphertext4, b'ad'), b'plaintext')
+    self.assertEqual(p4.decrypt(ciphertext4, b'ad'), b'plaintext')
+
+
+if __name__ == '__main__':
+  absltest.main()
diff --git a/tools/testing/supported_key_types.py b/testing/cross_language/util/supported_key_types.py
similarity index 81%
rename from tools/testing/supported_key_types.py
rename to testing/cross_language/util/supported_key_types.py
index 6ea2f32..91c7fa5 100644
--- a/tools/testing/supported_key_types.py
+++ b/testing/cross_language/util/supported_key_types.py
@@ -13,13 +13,14 @@
 
 # Placeholder for import for type annotations
 
+from typing import Iterable, List, Text, Tuple
+
 from tink import aead
 from tink import daead
 from tink import hybrid
 from tink import mac
 from tink import signature
-
-from typing import Iterable, List, Text, Tuple
+from tink import streaming_aead
 
 from tink.proto import tink_pb2
 
@@ -32,20 +33,24 @@
     'AesGcmKey',
     'AesCtrHmacAeadKey',
     'ChaCha20Poly1305Key',
-    'XChaCha20Poly1305Key'
+    'XChaCha20Poly1305Key',
 ]
 DAEAD_KEY_TYPES = ['AesSivKey']
+STREAMING_AEAD_KEY_TYPES = [
+    'AesCtrHmacStreamingKey',
+    'AesGcmHkdfStreamingKey',
+]
 HYBRID_PRIVATE_KEY_TYPES = ['EciesAeadHkdfPrivateKey']
 MAC_KEY_TYPES = ['HmacKey']
 SIGNATURE_KEY_TYPES = [
     'EcdsaPrivateKey',
     'Ed25519PrivateKey',
     'RsaSsaPkcs1PrivateKey',
-    'RsaSsaPssPrivateKey'
+    'RsaSsaPssPrivateKey',
 ]
 ALL_KEY_TYPES = (
-    AEAD_KEY_TYPES + DAEAD_KEY_TYPES + HYBRID_PRIVATE_KEY_TYPES +
-    MAC_KEY_TYPES + SIGNATURE_KEY_TYPES)
+    AEAD_KEY_TYPES + DAEAD_KEY_TYPES + STREAMING_AEAD_KEY_TYPES +
+    HYBRID_PRIVATE_KEY_TYPES + MAC_KEY_TYPES + SIGNATURE_KEY_TYPES)
 
 # All languages that are supported by a KeyType
 SUPPORTED_LANGUAGES = {
@@ -55,6 +60,8 @@
     'ChaCha20Poly1305Key': ['java', 'go'],
     'XChaCha20Poly1305Key': ['cc', 'java', 'go', 'python'],
     'AesSivKey': ['cc', 'java', 'go', 'python'],
+    'AesCtrHmacStreamingKey': ['cc', 'java'],
+    'AesGcmHkdfStreamingKey': ['cc', 'java', 'go'],
     'EciesAeadHkdfPrivateKey': ['cc', 'java', 'go', 'python'],
     'HmacKey': ['cc', 'java', 'go', 'python'],
     'EcdsaPrivateKey': ['cc', 'java', 'go', 'python'],
@@ -63,6 +70,10 @@
     'RsaSsaPssPrivateKey': ['cc', 'java', 'python'],
 }
 
+SUPPORTED_LANGUAGES_PER_TYPE_URL = {
+    'type.googleapis.com/google.crypto.tink.' + name: langs
+    for name, langs in SUPPORTED_LANGUAGES.items()}
+
 # For each KeyType, a list of all KeyTemplate Names that must be supported.
 KEY_TEMPLATE_NAMES = {
     'AesEaxKey': ['AES128_EAX', 'AES256_EAX'],
@@ -71,6 +82,15 @@
     'ChaCha20Poly1305Key': ['CHACHA20_POLY1305'],
     'XChaCha20Poly1305Key': ['XCHACHA20_POLY1305'],
     'AesSivKey': ['AES256_SIV'],
+    'AesCtrHmacStreamingKey': [
+        'AES128_CTR_HMAC_SHA256_4KB',
+        'AES256_CTR_HMAC_SHA256_4KB',
+    ],
+    'AesGcmHkdfStreamingKey': [
+        'AES128_GCM_HKDF_4KB',
+        'AES256_GCM_HKDF_4KB',
+        'AES256_GCM_HKDF_1MB',
+    ],
     'EciesAeadHkdfPrivateKey': [
         'ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM',
         'ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256'
@@ -123,6 +143,16 @@
         aead.aead_key_templates.XCHACHA20_POLY1305,
     'AES256_SIV':
         daead.deterministic_aead_key_templates.AES256_SIV,
+    'AES128_CTR_HMAC_SHA256_4KB':
+        streaming_aead.streaming_aead_key_templates.AES128_CTR_HMAC_SHA256_4KB,
+    'AES256_CTR_HMAC_SHA256_4KB':
+        streaming_aead.streaming_aead_key_templates.AES256_CTR_HMAC_SHA256_4KB,
+    'AES128_GCM_HKDF_4KB':
+        streaming_aead.streaming_aead_key_templates.AES128_GCM_HKDF_4KB,
+    'AES256_GCM_HKDF_4KB':
+        streaming_aead.streaming_aead_key_templates.AES256_GCM_HKDF_4KB,
+    'AES256_GCM_HKDF_1MB':
+        streaming_aead.streaming_aead_key_templates.AES256_GCM_HKDF_1MB,
     'ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM':
         hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM,
     'ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256':
diff --git a/tools/testing/supported_key_types_test.py b/testing/cross_language/util/supported_key_types_test.py
similarity index 92%
rename from tools/testing/supported_key_types_test.py
rename to testing/cross_language/util/supported_key_types_test.py
index a113024..9ea4c39 100644
--- a/tools/testing/supported_key_types_test.py
+++ b/testing/cross_language/util/supported_key_types_test.py
@@ -9,10 +9,10 @@
 # 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 tink.tools.testing.supported_key_types."""
+"""Tests for tink.testing.cross_language.supported_key_types."""
 
 from absl.testing import absltest
-from google3.third_party.tink.tools.testing import supported_key_types
+from util import supported_key_types
 
 
 class SupportedKeyTypesTest(absltest.TestCase):
diff --git a/testing/cross_language/util/testing_servers.py b/testing/cross_language/util/testing_servers.py
new file mode 100644
index 0000000..f1e908f
--- /dev/null
+++ b/testing/cross_language/util/testing_servers.py
@@ -0,0 +1,283 @@
+# 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.
+"""testing_server starts up testing gRPC servers in different languages."""
+
+from __future__ import absolute_import
+from __future__ import division
+# Placeholder for import for type annotations
+from __future__ import print_function
+
+import os
+import subprocess
+import time
+
+from typing import List, Text
+from absl import logging
+import grpc
+import portpicker
+
+from tink.proto import tink_pb2
+from proto.testing import testing_api_pb2
+from proto.testing import testing_api_pb2_grpc
+from util import _primitives
+
+# Server paths are relative to os.environ['testing_dir'], which can be set by:
+# bazel test util:testing_servers_test --test_env testing_dir=/tmp/tink/testing
+# If not set, the testing_dir is calcuated from os.path.abspath(__file__).
+_SERVER_PATHS = {
+    'cc': [
+        'cc/bazel-bin/testing_server',
+        'cc/testing_server'
+    ],
+    'go': [
+        'go/bazel-bin/linux_amd64_stripped/testing_server',
+        'go/testing_server'
+    ],
+    'java': [
+        'java_src/bazel-bin/testing_server_deploy.jar',
+        'java_src/testing_server'
+    ],
+    'python': [
+        'python/bazel-bin/testing_server',
+        'python/testing_server',
+    ]
+}
+
+# All languages that have a testing server
+LANGUAGES = list(_SERVER_PATHS.keys())
+
+# location of the testing_server java binary, relative to testing_dir
+_JAVA_PATH = 'java_src/bazel-bin/testing_server.runfiles/local_jdk/bin/java'
+
+_PRIMITIVE_STUBS = {
+    'aead': testing_api_pb2_grpc.AeadStub,
+    'daead': testing_api_pb2_grpc.DeterministicAeadStub,
+    'streaming_aead': testing_api_pb2_grpc.StreamingAeadStub,
+    'hybrid': testing_api_pb2_grpc.HybridStub,
+    'mac': testing_api_pb2_grpc.MacStub,
+    'signature': testing_api_pb2_grpc.SignatureStub,
+}
+
+# All primitives.
+_PRIMITIVES = list(_PRIMITIVE_STUBS.keys())
+
+SUPPORTED_LANGUAGES_BY_PRIMITIVE = {
+    'aead': ['cc', 'go', 'java', 'python'],
+    'daead': ['cc', 'go', 'java', 'python'],
+    'streaming_aead': ['cc', 'go', 'java'],
+    'hybrid': ['cc', 'go', 'java', 'python'],
+    'mac': ['cc', 'go', 'java', 'python'],
+    'signature': ['cc', 'go', 'java', 'python'],
+}
+
+
+def _server_path(lang: Text) -> Text:
+  """Returns the path where the server binary is located."""
+  if os.environ.get('testing_dir'):
+    testing_dir = os.environ.get('testing_dir')
+  else:
+    util_dir = os.path.dirname(os.path.abspath(__file__))
+    testing_dir = os.path.dirname(os.path.dirname(util_dir))
+  for relative_server_path in _SERVER_PATHS[lang]:
+    server_path = os.path.join(testing_dir, relative_server_path)
+    logging.info('try path: %s', server_path)
+    if os.path.exists(server_path):
+      return server_path
+  raise RuntimeError('Executable for lang %s not found' % lang)
+
+
+def _server_cmd(lang: Text, port: int) -> List[Text]:
+  server_path = _server_path(lang)
+  if lang == 'java' and server_path.endswith('.jar'):
+    java_path = os.path.join(os.environ.get('testing_dir'), _JAVA_PATH)
+    return [java_path, '-jar', server_path, '--port', '%d' % port]
+  else:
+    return [server_path, '--port', '%d' % port]
+
+
+class _TestingServers():
+  """TestingServers starts up testing gRPC servers and returns service stubs."""
+
+  def __init__(self):
+    self._server = {}
+    self._channel = {}
+    self._metadata_stub = {}
+    self._keyset_stub = {}
+    self._aead_stub = {}
+    self._daead_stub = {}
+    self._streaming_aead_stub = {}
+    self._hybrid_stub = {}
+    self._mac_stub = {}
+    self._signature_stub = {}
+    for lang in LANGUAGES:
+      port = portpicker.pick_unused_port()
+      cmd = _server_cmd(lang, port)
+      logging.info('cmd = %s', cmd)
+      self._server[lang] = subprocess.Popen(
+          cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+      logging.info('%s server started on port %d with pid: %d.',
+                   lang, port, self._server[lang].pid)
+      self._channel[lang] = grpc.secure_channel(
+          '[::]:%d' % port, grpc.local_channel_credentials())
+    for lang in LANGUAGES:
+      try:
+        grpc.channel_ready_future(self._channel[lang]).result(timeout=30)
+      except:
+        logging.info('Timeout while connecting to server %s', lang)
+        self._server[lang].kill()
+        out, err = self._server[lang].communicate()
+        raise RuntimeError(
+            'Could not start %s server, output=%s, err=%s' % (lang, out, err))
+      self._metadata_stub[lang] = testing_api_pb2_grpc.MetadataStub(
+          self._channel[lang])
+      self._keyset_stub[lang] = testing_api_pb2_grpc.KeysetStub(
+          self._channel[lang])
+    for primitive in _PRIMITIVES:
+      for lang in SUPPORTED_LANGUAGES_BY_PRIMITIVE[primitive]:
+        stub_name = '_%s_stub' % primitive
+        getattr(self, stub_name)[lang] = _PRIMITIVE_STUBS[primitive](
+            self._channel[lang])
+
+  def keyset_stub(self, lang) -> testing_api_pb2_grpc.KeysetStub:
+    return self._keyset_stub[lang]
+
+  def aead_stub(self, lang) -> testing_api_pb2_grpc.AeadStub:
+    return self._aead_stub[lang]
+
+  def daead_stub(self, lang) -> testing_api_pb2_grpc.DeterministicAeadStub:
+    return self._daead_stub[lang]
+
+  def streaming_aead_stub(self, lang) -> testing_api_pb2_grpc.StreamingAeadStub:
+    return self._streaming_aead_stub[lang]
+
+  def hybrid_stub(self, lang) -> testing_api_pb2_grpc.HybridStub:
+    return self._hybrid_stub[lang]
+
+  def mac_stub(self, lang) -> testing_api_pb2_grpc.MacStub:
+    return self._mac_stub[lang]
+
+  def signature_stub(self, lang) -> testing_api_pb2_grpc.SignatureStub:
+    return self._signature_stub[lang]
+
+  def metadata_stub(self, lang) -> testing_api_pb2_grpc.MetadataStub:
+    return self._metadata_stub[lang]
+
+  def stop(self):
+    """Stops all servers."""
+    logging.info('Stopping servers...')
+    for lang in LANGUAGES:
+      self._channel[lang].close()
+    for lang in LANGUAGES:
+      self._server[lang].terminate()
+    time.sleep(2)
+    for lang in LANGUAGES:
+      if self._server[lang].poll() is None:
+        logging.info('Killing server %s.', lang)
+        self._server[lang].kill()
+    logging.info('All servers stopped.')
+
+
+_ts = None
+
+
+def start() -> None:
+  """Starts all servers."""
+  global _ts
+  _ts = _TestingServers()
+
+  for lang in LANGUAGES:
+    response = _ts.metadata_stub(lang).GetServerInfo(
+        testing_api_pb2.ServerInfoRequest())
+    if lang != response.language:
+      raise ValueError(
+          'lang = %s != response.language = %s' % (lang, response.language))
+    logging.info('server_info:\n%s', response)
+
+
+def stop() -> None:
+  """Stops all servers."""
+  global _ts
+  _ts.stop()
+
+
+def new_keyset(lang: Text, key_template: tink_pb2.KeyTemplate) -> bytes:
+  """Returns a new KeysetHandle, implemented in lang."""
+  global _ts
+  return _primitives.new_keyset(_ts.keyset_stub(lang), key_template)
+
+
+def public_keyset(lang: Text, private_keyset: bytes) -> bytes:
+  """Returns a public keyset handle, implemented in lang."""
+  global _ts
+  return _primitives.public_keyset(_ts.keyset_stub(lang), private_keyset)
+
+
+def keyset_to_json(lang: Text, keyset: bytes) -> Text:
+  global _ts
+  return _primitives.keyset_to_json(_ts.keyset_stub(lang), keyset)
+
+
+def keyset_from_json(lang: Text, json_keyset: Text) -> bytes:
+  global _ts
+  return _primitives.keyset_from_json(_ts.keyset_stub(lang), json_keyset)
+
+
+def aead(lang: Text, keyset: bytes) -> _primitives.Aead:
+  """Returns an AEAD primitive, implemented in lang."""
+  global _ts
+  return _primitives.Aead(lang, _ts.aead_stub(lang), keyset)
+
+
+def deterministic_aead(lang: Text,
+                       keyset: bytes) -> _primitives.DeterministicAead:
+  """Returns a DeterministicAEAD primitive, implemented in lang."""
+  global _ts
+  return _primitives.DeterministicAead(lang, _ts.daead_stub(lang), keyset)
+
+
+def streaming_aead(lang: Text, key_handle: bytes) -> _primitives.StreamingAead:
+  """Returns a StreamingAEAD primitive, implemented in lang."""
+  global _ts
+  return _primitives.StreamingAead(
+      lang, _ts.streaming_aead_stub(lang), key_handle)
+
+
+def hybrid_encrypt(lang: Text, pub_keyset: bytes) -> _primitives.HybridEncrypt:
+  """Returns a HybridEncrypt  primitive, implemented in lang."""
+  global _ts
+  return _primitives.HybridEncrypt(lang, _ts.hybrid_stub(lang), pub_keyset)
+
+
+def hybrid_decrypt(lang: Text, priv_keyset: bytes) -> _primitives.HybridDecrypt:
+  """Returns a HybridDecrypt primitive, implemented in lang."""
+  global _ts
+  return _primitives.HybridDecrypt(lang, _ts.hybrid_stub(lang), priv_keyset)
+
+
+def mac(lang: Text, keyset: bytes) -> _primitives.Mac:
+  """Returns a MAC primitive, implemented in lang."""
+  global _ts
+  return _primitives.Mac(lang, _ts.mac_stub(lang), keyset)
+
+
+def public_key_sign(lang: Text,
+                    priv_keyset: bytes) -> _primitives.PublicKeySign:
+  """Returns an PublicKeySign primitive, implemented in lang."""
+  global _ts
+  return _primitives.PublicKeySign(lang, _ts.signature_stub(lang), priv_keyset)
+
+
+def public_key_verify(lang: Text,
+                      pub_keyset: bytes) -> _primitives.PublicKeyVerify:
+  """Returns an PublicKeyVerify primitive, implemented in lang."""
+  global _ts
+  return _primitives.PublicKeyVerify(lang, _ts.signature_stub(lang), pub_keyset)
diff --git a/testing/cross_language/util/testing_servers_test.py b/testing/cross_language/util/testing_servers_test.py
new file mode 100644
index 0000000..c19cf72
--- /dev/null
+++ b/testing/cross_language/util/testing_servers_test.py
@@ -0,0 +1,160 @@
+# 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 tink.testing.cross_language.util.testing_server."""
+
+import io
+
+from absl.testing import absltest
+from absl.testing import parameterized
+
+import tink
+from tink import aead
+from tink import daead
+from tink import hybrid
+from tink import mac
+from tink import signature
+from tink import streaming_aead
+
+from util import testing_servers
+
+_SUPPORTED_LANGUAGES = testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE
+
+
+class TestingServersConfigTest(absltest.TestCase):
+
+  def test_primitives(self):
+    self.assertEqual(
+        testing_servers._PRIMITIVE_STUBS.keys(),
+        _SUPPORTED_LANGUAGES.keys(),
+        msg=(
+            'The primitives specified as keys in '
+            'testing_servers._PRIMITIVE_STUBS must match the primitives '
+            ' specified as keys in '
+            'testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE.'
+        ))
+
+  def test_languages(self):
+    for primitive in _SUPPORTED_LANGUAGES:
+      languages = set(testing_servers.LANGUAGES)
+      supported_languages = set(_SUPPORTED_LANGUAGES[primitive])
+      self.assertContainsSubset(supported_languages, languages, msg=(
+          'The languages specified in '
+          'testing_servers.SUPPORTED_LANGUAGES_BY_PRIMITIVE must be a subset '
+          'of the languages specified in testing_servers.LANGUAGES.'
+      ))
+
+
+class TestingServersTest(parameterized.TestCase):
+
+  @classmethod
+  def setUpClass(cls):
+    super(TestingServersTest, cls).setUpClass()
+    testing_servers.start()
+
+  @classmethod
+  def tearDownClass(cls):
+    testing_servers.stop()
+    super(TestingServersTest, cls).tearDownClass()
+
+  @parameterized.parameters(_SUPPORTED_LANGUAGES['aead'])
+  def test_aead(self, lang):
+    keyset = testing_servers.new_keyset(lang,
+                                        aead.aead_key_templates.AES128_GCM)
+    plaintext = b'The quick brown fox jumps over the lazy dog'
+    associated_data = b'associated_data'
+    aead_primitive = testing_servers.aead(lang, keyset)
+    ciphertext = aead_primitive.encrypt(plaintext, associated_data)
+    output = aead_primitive.decrypt(ciphertext, associated_data)
+    self.assertEqual(output, plaintext)
+
+    with self.assertRaises(tink.TinkError):
+      aead_primitive.decrypt(b'foo', associated_data)
+
+  @parameterized.parameters(_SUPPORTED_LANGUAGES['daead'])
+  def test_daead(self, lang):
+    keyset = testing_servers.new_keyset(
+        lang, daead.deterministic_aead_key_templates.AES256_SIV)
+    plaintext = b'The quick brown fox jumps over the lazy dog'
+    associated_data = b'associated_data'
+    daead_primitive = testing_servers.deterministic_aead(lang, keyset)
+    ciphertext = daead_primitive.encrypt_deterministically(
+        plaintext, associated_data)
+    output = daead_primitive.decrypt_deterministically(
+        ciphertext, associated_data)
+    self.assertEqual(output, plaintext)
+
+    with self.assertRaises(tink.TinkError):
+      daead_primitive.decrypt_deterministically(b'foo', associated_data)
+
+  @parameterized.parameters(_SUPPORTED_LANGUAGES['streaming_aead'])
+  def test_streaming_aead(self, lang):
+    keyset = testing_servers.new_keyset(
+        lang, streaming_aead.streaming_aead_key_templates.AES128_GCM_HKDF_4KB)
+    plaintext = b'The quick brown fox jumps over the lazy dog'
+    plaintext_stream = io.BytesIO(plaintext)
+    associated_data = b'associated_data'
+    streaming_aead_primitive = testing_servers.streaming_aead(lang, keyset)
+    ciphertext_stream = streaming_aead_primitive.new_encrypting_stream(
+        plaintext_stream, associated_data)
+    output_stream = streaming_aead_primitive.new_decrypting_stream(
+        ciphertext_stream, associated_data)
+    self.assertEqual(output_stream.read(), plaintext)
+
+    with self.assertRaises(tink.TinkError):
+      streaming_aead_primitive.new_decrypting_stream(io.BytesIO(b'foo'),
+                                                     associated_data)
+
+  @parameterized.parameters(_SUPPORTED_LANGUAGES['mac'])
+  def test_mac(self, lang):
+    keyset = testing_servers.new_keyset(
+        lang, mac.mac_key_templates.HMAC_SHA256_128BITTAG)
+    data = b'The quick brown fox jumps over the lazy dog'
+    mac_primitive = testing_servers.mac(lang, keyset)
+    mac_value = mac_primitive.compute_mac(data)
+    mac_primitive.verify_mac(mac_value, data)
+
+    with self.assertRaises(tink.TinkError):
+      mac_primitive.verify_mac(b'foo', data)
+
+  @parameterized.parameters(_SUPPORTED_LANGUAGES['hybrid'])
+  def test_hybrid(self, lang):
+    private_handle = testing_servers.new_keyset(
+        lang,
+        hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
+    public_handle = testing_servers.public_keyset(lang, private_handle)
+    enc_primitive = testing_servers.hybrid_encrypt(lang, public_handle)
+    data = b'The quick brown fox jumps over the lazy dog'
+    context_info = b'context'
+    ciphertext = enc_primitive.encrypt(data, context_info)
+    dec_primitive = testing_servers.hybrid_decrypt(lang, private_handle)
+    output = dec_primitive.decrypt(ciphertext, context_info)
+    self.assertEqual(output, data)
+
+    with self.assertRaises(tink.TinkError):
+      dec_primitive.decrypt(b'foo', context_info)
+
+  @parameterized.parameters(_SUPPORTED_LANGUAGES['signature'])
+  def test_signature(self, lang):
+    private_handle = testing_servers.new_keyset(
+        lang, signature.signature_key_templates.ED25519)
+    public_handle = testing_servers.public_keyset(lang, private_handle)
+    sign_primitive = testing_servers.public_key_sign(lang, private_handle)
+    data = b'The quick brown fox jumps over the lazy dog'
+    signature_value = sign_primitive.sign(data)
+    verify_primitive = testing_servers.public_key_verify(lang, public_handle)
+    verify_primitive.verify(signature_value, data)
+
+    with self.assertRaises(tink.TinkError):
+      verify_primitive.verify(b'foo', data)
+
+if __name__ == '__main__':
+  absltest.main()
diff --git a/testing/go/.bazelversion b/testing/go/.bazelversion
new file mode 100644
index 0000000..fd2a018
--- /dev/null
+++ b/testing/go/.bazelversion
@@ -0,0 +1 @@
+3.1.0
diff --git a/testing/go/BUILD.bazel b/testing/go/BUILD.bazel
new file mode 100644
index 0000000..f4595da
--- /dev/null
+++ b/testing/go/BUILD.bazel
@@ -0,0 +1,73 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
+load("@rules_proto_grpc//go:defs.bzl", "go_grpc_library")
+
+package(
+    default_testonly = 1,
+    default_visibility = ["//:__subpackages__"],
+)
+
+licenses(["notice"])
+
+go_grpc_library(
+    name = "testing_api_go_grpc",
+    importpath = "github.com/google/tink/proto/testing/testing_api_go_grpc",
+    deps = ["@tink_base//proto/testing:testing_api_proto"],
+)
+
+go_library(
+    name = "services",
+    srcs = [
+        "aead_service.go",
+        "daead_service.go",
+        "hybrid_service.go",
+        "keyset_service.go",
+        "mac_service.go",
+        "metadata_service.go",
+        "signature_service.go",
+        "streaming_aead_service.go",
+    ],
+    importpath = "github.com/google/tink/testing/go/services",
+    deps = [
+        ":testing_api_go_grpc",
+        "@com_github_golang_protobuf//proto:go_default_library",
+        "@tink_go//aead:go_default_library",
+        "@tink_go//daead:go_default_library",
+        "@tink_go//hybrid:go_default_library",
+        "@tink_go//keyset:go_default_library",
+        "@tink_go//mac:go_default_library",
+        "@tink_go//proto:tink_go_proto",
+        "@tink_go//signature:go_default_library",
+        "@tink_go//streamingaead:go_default_library",
+        "@tink_go//testkeyset:go_default_library",
+    ],
+)
+
+go_test(
+    name = "services_test",
+    size = "small",
+    srcs = ["services_test.go"],
+    deps = [
+        ":services",
+        ":testing_api_go_grpc",
+        "@com_github_golang_protobuf//proto:go_default_library",
+        "@tink_go//aead:go_default_library",
+        "@tink_go//daead:go_default_library",
+        "@tink_go//hybrid:go_default_library",
+        "@tink_go//keyset:go_default_library",
+        "@tink_go//mac:go_default_library",
+        "@tink_go//signature:go_default_library",
+        "@tink_go//streamingaead:go_default_library",
+    ],
+)
+
+go_binary(
+    name = "testing_server",
+    srcs = [
+        "testing_server.go",
+    ],
+    deps = [
+        ":services",
+        ":testing_api_go_grpc",
+        "@org_golang_google_grpc//:go_default_library",
+    ],
+)
diff --git a/testing/go/WORKSPACE b/testing/go/WORKSPACE
new file mode 100644
index 0000000..c9ef526
--- /dev/null
+++ b/testing/go/WORKSPACE
@@ -0,0 +1,302 @@
+workspace(name = "testing_go")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+local_repository(
+    name = "tink_base",
+    path = "../..",
+)
+
+local_repository(
+    name = "tink_go",
+    path = "../../go",
+)
+
+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()
+
+load("@tink_go//:tink_go_deps.bzl", "tink_go_deps")
+tink_go_deps()
+
+http_archive(
+    name = "rules_proto",
+    sha256 = "602e7161d9195e50246177e7c55b2f39950a9cf7366f74ed5f22fd45750cd208",
+    strip_prefix = "rules_proto-97d8af4dc474595af3900dd85cb3a29ad28cc313",
+    urls = [
+        "https://mirror.bazel.build/github.com/bazelbuild/rules_proto/archive/97d8af4dc474595af3900dd85cb3a29ad28cc313.tar.gz",
+        "https://github.com/bazelbuild/rules_proto/archive/97d8af4dc474595af3900dd85cb3a29ad28cc313.tar.gz",
+    ],
+)
+load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
+rules_proto_dependencies()
+rules_proto_toolchains()
+
+http_archive(
+    name = "rules_proto_grpc",
+    urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/1.0.2.tar.gz"],
+    sha256 = "5f0f2fc0199810c65a2de148a52ba0aff14d631d4e8202f41aff6a9d590a471b",
+    strip_prefix = "rules_proto_grpc-1.0.2",
+)
+
+load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_toolchains", "rules_proto_grpc_repos", "bazel_gazelle", "io_bazel_rules_go")
+
+rules_proto_grpc_toolchains()
+rules_proto_grpc_repos()
+
+io_bazel_rules_go()
+
+# Go initialization does not work via tink_go_deps_init for some reason.
+# All of the Go-init stuff is for now just copied from Go-WORKSPACE.
+load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
+
+go_rules_dependencies()
+go_register_toolchains(nogo = "@//go:tink_nogo")
+
+bazel_gazelle()
+
+load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
+
+gazelle_dependencies()
+
+load("@rules_proto_grpc//go:repositories.bzl", rules_proto_grpc_go_repos="go_repos")
+
+rules_proto_grpc_go_repos()
+
+# How to update go dependencies:
+# 1) Remove all go_repository rules in WORKSPACE.bazel
+# 2) Update the files go.mod and go.sum. This can be done as follows:
+#    2.1) Replacing all versions in go.mod with "latest".
+#    2.2) Run "go mod tidy".
+# 3) Update the WORKSPACE.bazel file by running
+#    bazel run //:gazelle -- update-repos -from_file=go.mod
+# Put the go repository rules in the right place.
+go_repository(
+    name = "co_honnef_go_tools",
+    importpath = "honnef.co/go/tools",
+    sum = "h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA=",
+    version = "v0.0.0-20190418001031-e561f6794a2a",
+)
+go_repository(
+    name = "com_github_aws_aws_sdk_go",
+    importpath = "github.com/aws/aws-sdk-go",
+    sum = "h1:1xxya3nsUaFlEZuoE5PWsIEd47RoDV/kkOGt0qEuwNw=",
+    version = "v1.25.39",
+)
+go_repository(
+    name = "com_github_burntsushi_toml",
+    importpath = "github.com/BurntSushi/toml",
+    sum = "h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=",
+    version = "v0.3.1",
+)
+go_repository(
+    name = "com_github_client9_misspell",
+    importpath = "github.com/client9/misspell",
+    sum = "h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=",
+    version = "v0.3.4",
+)
+go_repository(
+    name = "com_github_davecgh_go_spew",
+    importpath = "github.com/davecgh/go-spew",
+    sum = "h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=",
+    version = "v1.1.0",
+)
+go_repository(
+    name = "com_github_golang_glog",
+    importpath = "github.com/golang/glog",
+    sum = "h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=",
+    version = "v0.0.0-20160126235308-23def4e6c14b",
+)
+go_repository(
+    name = "com_github_golang_mock",
+    importpath = "github.com/golang/mock",
+    sum = "h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=",
+    version = "v1.2.0",
+)
+go_repository(
+    name = "com_github_golang_protobuf",
+    importpath = "github.com/golang/protobuf",
+    sum = "h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=",
+    version = "v1.3.2",
+)
+go_repository(
+    name = "com_github_google_btree",
+    importpath = "github.com/google/btree",
+    sum = "h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=",
+    version = "v0.0.0-20180813153112-4030bb1f1f0c",
+)
+go_repository(
+    name = "com_github_google_go_cmp",
+    importpath = "github.com/google/go-cmp",
+    sum = "h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=",
+    version = "v0.3.0",
+)
+go_repository(
+    name = "com_github_google_martian",
+    importpath = "github.com/google/martian",
+    sum = "h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=",
+    version = "v2.1.0+incompatible",
+)
+go_repository(
+    name = "com_github_google_pprof",
+    importpath = "github.com/google/pprof",
+    sum = "h1:eqyIo2HjKhKe/mJzTG8n4VqvLXIOEG+SLdDqX7xGtkY=",
+    version = "v0.0.0-20181206194817-3ea8567a2e57",
+)
+go_repository(
+    name = "com_github_googleapis_gax_go_v2",
+    importpath = "github.com/googleapis/gax-go/v2",
+    sum = "h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=",
+    version = "v2.0.5",
+)
+go_repository(
+    name = "com_github_hashicorp_golang_lru",
+    importpath = "github.com/hashicorp/golang-lru",
+    sum = "h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=",
+    version = "v0.5.1",
+)
+go_repository(
+    name = "com_github_jmespath_go_jmespath",
+    importpath = "github.com/jmespath/go-jmespath",
+    sum = "h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=",
+    version = "v0.0.0-20180206201540-c2b33e8439af",
+)
+go_repository(
+    name = "com_github_jstemmer_go_junit_report",
+    importpath = "github.com/jstemmer/go-junit-report",
+    sum = "h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=",
+    version = "v0.0.0-20190106144839-af01ea7f8024",
+)
+go_repository(
+    name = "com_github_pmezard_go_difflib",
+    importpath = "github.com/pmezard/go-difflib",
+    sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=",
+    version = "v1.0.0",
+)
+go_repository(
+    name = "com_github_stretchr_objx",
+    importpath = "github.com/stretchr/objx",
+    sum = "h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=",
+    version = "v0.1.0",
+)
+go_repository(
+    name = "com_github_stretchr_testify",
+    importpath = "github.com/stretchr/testify",
+    sum = "h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=",
+    version = "v1.4.0",
+)
+go_repository(
+    name = "com_google_cloud_go",
+    importpath = "cloud.google.com/go",
+    sum = "h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=",
+    version = "v0.38.0",
+)
+go_repository(
+    name = "in_gopkg_check_v1",
+    importpath = "gopkg.in/check.v1",
+    sum = "h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=",
+    version = "v0.0.0-20161208181325-20d25e280405",
+)
+go_repository(
+    name = "in_gopkg_yaml_v2",
+    importpath = "gopkg.in/yaml.v2",
+    sum = "h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=",
+    version = "v2.2.2",
+)
+go_repository(
+    name = "io_opencensus_go",
+    importpath = "go.opencensus.io",
+    sum = "h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=",
+    version = "v0.21.0",
+)
+go_repository(
+    name = "org_golang_google_api",
+    importpath = "google.golang.org/api",
+    sum = "h1:uMf5uLi4eQMRrMKhCplNik4U4H8Z6C1br3zOtAa/aDE=",
+    version = "v0.14.0",
+)
+go_repository(
+    name = "org_golang_google_appengine",
+    importpath = "google.golang.org/appengine",
+    sum = "h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=",
+    version = "v1.5.0",
+)
+go_repository(
+    name = "org_golang_google_genproto",
+    importpath = "google.golang.org/genproto",
+    sum = "h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=",
+    version = "v0.0.0-20190502173448-54afdca5d873",
+)
+go_repository(
+    name = "org_golang_google_grpc",
+    importpath = "google.golang.org/grpc",
+    sum = "h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=",
+    version = "v1.20.1",
+)
+go_repository(
+    name = "org_golang_x_crypto",
+    importpath = "golang.org/x/crypto",
+    sum = "h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE=",
+    version = "v0.0.0-20191119213627-4f8c1d86b1ba",
+)
+go_repository(
+    name = "org_golang_x_exp",
+    importpath = "golang.org/x/exp",
+    sum = "h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=",
+    version = "v0.0.0-20190121172915-509febef88a4",
+)
+go_repository(
+    name = "org_golang_x_lint",
+    importpath = "golang.org/x/lint",
+    sum = "h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=",
+    version = "v0.0.0-20190409202823-959b441ac422",
+)
+go_repository(
+    name = "org_golang_x_net",
+    importpath = "golang.org/x/net",
+    sum = "h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=",
+    version = "v0.0.0-20190503192946-f4e77d36d62c",
+)
+go_repository(
+    name = "org_golang_x_oauth2",
+    importpath = "golang.org/x/oauth2",
+    sum = "h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=",
+    version = "v0.0.0-20190604053449-0f29369cfe45",
+)
+go_repository(
+    name = "org_golang_x_sync",
+    importpath = "golang.org/x/sync",
+    sum = "h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=",
+    version = "v0.0.0-20190423024810-112230192c58",
+)
+go_repository(
+    name = "org_golang_x_sys",
+    importpath = "golang.org/x/sys",
+    sum = "h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=",
+    version = "v0.0.0-20190507160741-ecd444e8653b",
+)
+go_repository(
+    name = "org_golang_x_text",
+    importpath = "golang.org/x/text",
+    sum = "h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=",
+    version = "v0.3.2",
+)
+go_repository(
+    name = "org_golang_x_time",
+    importpath = "golang.org/x/time",
+    sum = "h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg=",
+    version = "v0.0.0-20181108054448-85acf8d2951c",
+)
+go_repository(
+    name = "org_golang_x_tools",
+    importpath = "golang.org/x/tools",
+    sum = "h1:97SnQk1GYRXJgvwZ8fadnxDOWfKvkNQHH3CtZntPSrM=",
+    version = "v0.0.0-20190506145303-2d16b83fe98c",
+)
+go_repository(
+    name = "com_github_hashicorp_vault",
+    importpath = "github.com/hashicorp/vault",
+    tag = "v1.2.3",
+)
diff --git a/testing/go/aead_service.go b/testing/go/aead_service.go
new file mode 100644
index 0000000..548447b
--- /dev/null
+++ b/testing/go/aead_service.go
@@ -0,0 +1,71 @@
+// 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 services
+
+import (
+	"bytes"
+	"context"
+
+	"github.com/google/tink/go/aead"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/testkeyset"
+	pb "github.com/google/tink/proto/testing/testing_api_go_grpc"
+)
+
+// AEADService implements the Aead testing service.
+type AEADService struct {
+}
+
+func (s *AEADService) Encrypt(ctx context.Context, req *pb.AeadEncryptRequest) (*pb.AeadEncryptResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.Keyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.AeadEncryptResponse{
+			Result: &pb.AeadEncryptResponse_Err{err.Error()}}, nil
+	}
+	cipher, err := aead.New(handle)
+	if err != nil {
+		return &pb.AeadEncryptResponse{
+			Result: &pb.AeadEncryptResponse_Err{err.Error()}}, nil
+	}
+	ciphertext, err := cipher.Encrypt(req.Plaintext, req.AssociatedData)
+	if err != nil {
+		return &pb.AeadEncryptResponse{
+			Result: &pb.AeadEncryptResponse_Err{err.Error()}}, nil
+	}
+	return &pb.AeadEncryptResponse{
+		Result: &pb.AeadEncryptResponse_Ciphertext{ciphertext}}, nil
+}
+
+func (s *AEADService) Decrypt(ctx context.Context, req *pb.AeadDecryptRequest) (*pb.AeadDecryptResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.Keyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.AeadDecryptResponse{
+			Result: &pb.AeadDecryptResponse_Err{err.Error()}}, nil
+	}
+	cipher, err := aead.New(handle)
+	if err != nil {
+		return &pb.AeadDecryptResponse{
+			Result: &pb.AeadDecryptResponse_Err{err.Error()}}, nil
+	}
+	plaintext, err := cipher.Decrypt(req.Ciphertext, req.AssociatedData)
+	if err != nil {
+		return &pb.AeadDecryptResponse{
+			Result: &pb.AeadDecryptResponse_Err{err.Error()}}, nil
+	}
+	return &pb.AeadDecryptResponse{
+		Result: &pb.AeadDecryptResponse_Plaintext{plaintext}}, nil
+}
diff --git a/testing/go/daead_service.go b/testing/go/daead_service.go
new file mode 100644
index 0000000..b15b681
--- /dev/null
+++ b/testing/go/daead_service.go
@@ -0,0 +1,71 @@
+// 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 services
+
+import (
+	"bytes"
+	"context"
+
+	"github.com/google/tink/go/daead"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/testkeyset"
+	pb "github.com/google/tink/proto/testing/testing_api_go_grpc"
+)
+
+// DeterministicAEADService implements the DeterministicAead testing service.
+type DeterministicAEADService struct {
+}
+
+func (s *DeterministicAEADService) EncryptDeterministically(ctx context.Context, req *pb.DeterministicAeadEncryptRequest) (*pb.DeterministicAeadEncryptResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.Keyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.DeterministicAeadEncryptResponse{
+			Result: &pb.DeterministicAeadEncryptResponse_Err{err.Error()}}, nil
+	}
+	cipher, err := daead.New(handle)
+	if err != nil {
+		return &pb.DeterministicAeadEncryptResponse{
+			Result: &pb.DeterministicAeadEncryptResponse_Err{err.Error()}}, nil
+	}
+	ciphertext, err := cipher.EncryptDeterministically(req.Plaintext, req.AssociatedData)
+	if err != nil {
+		return &pb.DeterministicAeadEncryptResponse{
+			Result: &pb.DeterministicAeadEncryptResponse_Err{err.Error()}}, nil
+	}
+	return &pb.DeterministicAeadEncryptResponse{
+		Result: &pb.DeterministicAeadEncryptResponse_Ciphertext{ciphertext}}, nil
+}
+
+func (s *DeterministicAEADService) DecryptDeterministically(ctx context.Context, req *pb.DeterministicAeadDecryptRequest) (*pb.DeterministicAeadDecryptResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.Keyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.DeterministicAeadDecryptResponse{
+			Result: &pb.DeterministicAeadDecryptResponse_Err{err.Error()}}, nil
+	}
+	cipher, err := daead.New(handle)
+	if err != nil {
+		return &pb.DeterministicAeadDecryptResponse{
+			Result: &pb.DeterministicAeadDecryptResponse_Err{err.Error()}}, nil
+	}
+	plaintext, err := cipher.DecryptDeterministically(req.Ciphertext, req.AssociatedData)
+	if err != nil {
+		return &pb.DeterministicAeadDecryptResponse{
+			Result: &pb.DeterministicAeadDecryptResponse_Err{err.Error()}}, nil
+	}
+	return &pb.DeterministicAeadDecryptResponse{
+		Result: &pb.DeterministicAeadDecryptResponse_Plaintext{plaintext}}, nil
+}
diff --git a/testing/go/hybrid_service.go b/testing/go/hybrid_service.go
new file mode 100644
index 0000000..af1271e
--- /dev/null
+++ b/testing/go/hybrid_service.go
@@ -0,0 +1,71 @@
+// 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 services
+
+import (
+	"bytes"
+	"context"
+
+	"github.com/google/tink/go/hybrid"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/testkeyset"
+	pb "github.com/google/tink/proto/testing/testing_api_go_grpc"
+)
+
+// HybridService implements the Hybrid encryption and decryption testing service.
+type HybridService struct {
+}
+
+func (s *HybridService) Encrypt(ctx context.Context, req *pb.HybridEncryptRequest) (*pb.HybridEncryptResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.PublicKeyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.HybridEncryptResponse{
+			Result: &pb.HybridEncryptResponse_Err{err.Error()}}, nil
+	}
+	cipher, err := hybrid.NewHybridEncrypt(handle)
+	if err != nil {
+		return &pb.HybridEncryptResponse{
+			Result: &pb.HybridEncryptResponse_Err{err.Error()}}, nil
+	}
+	ciphertext, err := cipher.Encrypt(req.Plaintext, req.ContextInfo)
+	if err != nil {
+		return &pb.HybridEncryptResponse{
+			Result: &pb.HybridEncryptResponse_Err{err.Error()}}, nil
+	}
+	return &pb.HybridEncryptResponse{
+		Result: &pb.HybridEncryptResponse_Ciphertext{ciphertext}}, nil
+}
+
+func (s *HybridService) Decrypt(ctx context.Context, req *pb.HybridDecryptRequest) (*pb.HybridDecryptResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.PrivateKeyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.HybridDecryptResponse{
+			Result: &pb.HybridDecryptResponse_Err{err.Error()}}, nil
+	}
+	cipher, err := hybrid.NewHybridDecrypt(handle)
+	if err != nil {
+		return &pb.HybridDecryptResponse{
+			Result: &pb.HybridDecryptResponse_Err{err.Error()}}, nil
+	}
+	plaintext, err := cipher.Decrypt(req.Ciphertext, req.ContextInfo)
+	if err != nil {
+		return &pb.HybridDecryptResponse{
+			Result: &pb.HybridDecryptResponse_Err{err.Error()}}, nil
+	}
+	return &pb.HybridDecryptResponse{
+		Result: &pb.HybridDecryptResponse_Plaintext{plaintext}}, nil
+}
diff --git a/testing/go/keyset_service.go b/testing/go/keyset_service.go
new file mode 100644
index 0000000..39143e9
--- /dev/null
+++ b/testing/go/keyset_service.go
@@ -0,0 +1,112 @@
+// 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 services is implements gRPC services for testing_api.
+package services
+
+import (
+	"bytes"
+	"context"
+
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/testkeyset"
+	pb "github.com/google/tink/proto/testing/testing_api_go_grpc"
+	tinkpb "github.com/google/tink/go/proto/tink_go_proto"
+
+	"github.com/golang/protobuf/proto"
+)
+
+// KeysetService implements the Keyset testing service.
+type KeysetService struct {
+}
+
+func (s *KeysetService) Generate(ctx context.Context, req *pb.KeysetGenerateRequest) (*pb.KeysetGenerateResponse, error) {
+	template := &tinkpb.KeyTemplate{}
+	err := proto.Unmarshal(req.Template, template)
+	if err != nil {
+		return &pb.KeysetGenerateResponse{
+			Result: &pb.KeysetGenerateResponse_Err{err.Error()}}, nil
+	}
+	handle, err := keyset.NewHandle(template)
+	if err != nil {
+		return &pb.KeysetGenerateResponse{
+			Result: &pb.KeysetGenerateResponse_Err{err.Error()}}, nil
+	}
+	buf := new(bytes.Buffer)
+	writer := keyset.NewBinaryWriter(buf)
+	err = testkeyset.Write(handle, writer)
+	if err != nil {
+		return &pb.KeysetGenerateResponse{
+			Result: &pb.KeysetGenerateResponse_Err{err.Error()}}, nil
+	}
+	return &pb.KeysetGenerateResponse{
+		Result: &pb.KeysetGenerateResponse_Keyset{buf.Bytes()}}, nil
+}
+
+func (s *KeysetService) Public(ctx context.Context, req *pb.KeysetPublicRequest) (*pb.KeysetPublicResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.PrivateKeyset))
+	privateHandle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.KeysetPublicResponse{
+			Result: &pb.KeysetPublicResponse_Err{err.Error()}}, nil
+	}
+	publicHandle, err := privateHandle.Public()
+	if err != nil {
+		return &pb.KeysetPublicResponse{
+			Result: &pb.KeysetPublicResponse_Err{err.Error()}}, nil
+	}
+	buf := new(bytes.Buffer)
+	writer := keyset.NewBinaryWriter(buf)
+	err = testkeyset.Write(publicHandle, writer)
+	if err != nil {
+		return &pb.KeysetPublicResponse{
+			Result: &pb.KeysetPublicResponse_Err{err.Error()}}, nil
+	}
+	return &pb.KeysetPublicResponse{
+		Result: &pb.KeysetPublicResponse_PublicKeyset{buf.Bytes()}}, nil
+}
+
+func (s *KeysetService) ToJson(ctx context.Context, req *pb.KeysetToJsonRequest) (*pb.KeysetToJsonResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.Keyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.KeysetToJsonResponse{
+			Result: &pb.KeysetToJsonResponse_Err{err.Error()}}, nil
+	}
+	buf := new(bytes.Buffer)
+	writer := keyset.NewJSONWriter(buf)
+	if err := testkeyset.Write(handle, writer); err != nil {
+		return &pb.KeysetToJsonResponse{
+			Result: &pb.KeysetToJsonResponse_Err{err.Error()}}, nil
+	}
+	return &pb.KeysetToJsonResponse{
+		Result: &pb.KeysetToJsonResponse_JsonKeyset{buf.String()}}, nil
+}
+
+func (s *KeysetService) FromJson(ctx context.Context, req *pb.KeysetFromJsonRequest) (*pb.KeysetFromJsonResponse, error) {
+	reader := keyset.NewJSONReader(bytes.NewBufferString(req.JsonKeyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.KeysetFromJsonResponse{
+			Result: &pb.KeysetFromJsonResponse_Err{err.Error()}}, nil
+	}
+	buf := new(bytes.Buffer)
+	writer := keyset.NewBinaryWriter(buf)
+	if err := testkeyset.Write(handle, writer); err != nil {
+		return &pb.KeysetFromJsonResponse{
+			Result: &pb.KeysetFromJsonResponse_Err{err.Error()}}, nil
+	}
+	return &pb.KeysetFromJsonResponse{
+		Result: &pb.KeysetFromJsonResponse_Keyset{buf.Bytes()}}, nil
+}
diff --git a/testing/go/mac_service.go b/testing/go/mac_service.go
new file mode 100644
index 0000000..ed733c4
--- /dev/null
+++ b/testing/go/mac_service.go
@@ -0,0 +1,67 @@
+// 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 services
+
+import (
+	"bytes"
+	"context"
+
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/mac"
+	"github.com/google/tink/go/testkeyset"
+	pb "github.com/google/tink/proto/testing/testing_api_go_grpc"
+)
+
+// MacService implements the MAC testing service.
+type MacService struct {
+}
+
+func (s *MacService) ComputeMac(ctx context.Context, req *pb.ComputeMacRequest) (*pb.ComputeMacResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.Keyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.ComputeMacResponse{
+			Result: &pb.ComputeMacResponse_Err{err.Error()}}, nil
+	}
+	primitive, err := mac.New(handle)
+	if err != nil {
+		return &pb.ComputeMacResponse{
+			Result: &pb.ComputeMacResponse_Err{err.Error()}}, nil
+	}
+	macValue, err := primitive.ComputeMAC(req.Data)
+	if err != nil {
+		return &pb.ComputeMacResponse{
+			Result: &pb.ComputeMacResponse_Err{err.Error()}}, nil
+	}
+	return &pb.ComputeMacResponse{
+		Result: &pb.ComputeMacResponse_MacValue{macValue}}, nil
+}
+
+func (s *MacService) VerifyMac(ctx context.Context, req *pb.VerifyMacRequest) (*pb.VerifyMacResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.Keyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.VerifyMacResponse{Err: err.Error()}, nil
+	}
+	primitive, err := mac.New(handle)
+	if err != nil {
+		return &pb.VerifyMacResponse{Err: err.Error()}, nil
+	}
+	err = primitive.VerifyMAC(req.MacValue, req.Data)
+	if err != nil {
+		return &pb.VerifyMacResponse{Err: err.Error()}, nil
+	}
+	return &pb.VerifyMacResponse{}, nil
+}
diff --git a/testing/go/metadata_service.go b/testing/go/metadata_service.go
new file mode 100644
index 0000000..7fa0847
--- /dev/null
+++ b/testing/go/metadata_service.go
@@ -0,0 +1,31 @@
+// 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 services
+
+import (
+	"context"
+
+	pb "github.com/google/tink/proto/testing/testing_api_go_grpc"
+)
+
+// MetadataService implements the Keyset testing service.
+type MetadataService struct {
+}
+
+func (s *MetadataService) GetServerInfo(ctx context.Context, req *pb.ServerInfoRequest) (*pb.ServerInfoResponse, error) {
+	return &pb.ServerInfoResponse{
+		Language: "go",
+	}, nil
+}
diff --git a/testing/go/services_test.go b/testing/go/services_test.go
new file mode 100644
index 0000000..d863fff
--- /dev/null
+++ b/testing/go/services_test.go
@@ -0,0 +1,655 @@
+// 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 services_test
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/google/tink/go/aead"
+	"github.com/google/tink/go/daead"
+	"github.com/google/tink/go/hybrid"
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/mac"
+	"github.com/google/tink/go/signature"
+	"github.com/google/tink/go/streamingaead"
+	pb "github.com/google/tink/proto/testing/testing_api_go_grpc"
+	"github.com/google/tink/testing/go/services"
+)
+
+func genKeyset(ctx context.Context, keysetService *services.KeysetService, template []byte) ([]byte, error) {
+	genRequest := &pb.KeysetGenerateRequest{Template: template}
+	genResponse, err := keysetService.Generate(ctx, genRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := genResponse.Result.(type) {
+	case *pb.KeysetGenerateResponse_Keyset:
+		return r.Keyset, nil
+	case *pb.KeysetGenerateResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("genResponse.Result has unexpected type %T", r)
+	}
+}
+
+func pubKeyset(ctx context.Context, keysetService *services.KeysetService, privateKeyset []byte) ([]byte, error) {
+	request := &pb.KeysetPublicRequest{PrivateKeyset: privateKeyset}
+	response, err := keysetService.Public(ctx, request)
+	if err != nil {
+		return nil, err
+	}
+	switch r := response.Result.(type) {
+	case *pb.KeysetPublicResponse_PublicKeyset:
+		return r.PublicKeyset, nil
+	case *pb.KeysetPublicResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("response.Result has unexpected type %T", r)
+	}
+}
+
+func keysetFromJSON(ctx context.Context, keysetService *services.KeysetService, jsonKeyset string) ([]byte, error) {
+	request := &pb.KeysetFromJsonRequest{JsonKeyset: jsonKeyset}
+	response, err := keysetService.FromJson(ctx, request)
+	if err != nil {
+		return nil, err
+	}
+	switch r := response.Result.(type) {
+	case *pb.KeysetFromJsonResponse_Keyset:
+		return r.Keyset, nil
+	case *pb.KeysetFromJsonResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("response.Result has unexpected type %T", r)
+	}
+}
+
+func keysetToJSON(ctx context.Context, keysetService *services.KeysetService, keyset []byte) (string, error) {
+	request := &pb.KeysetToJsonRequest{Keyset: keyset}
+	response, err := keysetService.ToJson(ctx, request)
+	if err != nil {
+		return "", err
+	}
+	switch r := response.Result.(type) {
+	case *pb.KeysetToJsonResponse_JsonKeyset:
+		return r.JsonKeyset, nil
+	case *pb.KeysetToJsonResponse_Err:
+		return "", errors.New(r.Err)
+	default:
+		return "", fmt.Errorf("response.Result has unexpected type %T", r)
+	}
+}
+
+func TestFromJSON(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	ctx := context.Background()
+	jsonKeyset := `
+        {
+          "primaryKeyId": 42,
+          "key": [
+            {
+              "keyData": {
+                "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+                "keyMaterialType": "SYMMETRIC",
+                "value": "GhCS/1+ejWpx68NfGt6ziYHd"
+              },
+              "outputPrefixType": "TINK",
+              "keyId": 42,
+              "status": "ENABLED"
+            }
+          ]
+        }`
+	keysetData, err := keysetFromJSON(ctx, keysetService, jsonKeyset)
+	if err != nil {
+		t.Fatalf("keysetFromJSON failed: %v", err)
+	}
+	reader := keyset.NewBinaryReader(bytes.NewReader(keysetData))
+	keyset, err := reader.Read()
+	if err != nil {
+		t.Fatalf("reader.Read() failed: %v", err)
+	}
+	if keyset.GetPrimaryKeyId() != 42 {
+		t.Fatalf("Got keyset.GetPrimaryKeyId() == %d, want 42", keyset.GetPrimaryKeyId())
+	}
+}
+
+func TestGenerateToFromJSON(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	ctx := context.Background()
+
+	template, err := proto.Marshal(aead.AES128GCMKeyTemplate())
+	if err != nil {
+		t.Fatalf("proto.Marshal(aead.AES128GCMKeyTemplate()) failed: %v", err)
+	}
+	keyset, err := genKeyset(ctx, keysetService, template)
+	if err != nil {
+		t.Fatalf("genKeyset failed: %v", err)
+	}
+	jsonKeyset, err := keysetToJSON(ctx, keysetService, keyset)
+	if err != nil {
+		t.Fatalf("keysetToJSON failed: %v", err)
+	}
+	output, err := keysetFromJSON(ctx, keysetService, jsonKeyset)
+	if err != nil {
+		t.Fatalf("keysetFromJSON failed: %v", err)
+	}
+	if bytes.Compare(output, keyset) != 0 {
+		t.Fatalf("output is %v, want %v", output, keyset)
+	}
+}
+
+func TestKeysetFromJSONFail(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	ctx := context.Background()
+	if _, err := keysetFromJSON(ctx, keysetService, "bad JSON"); err == nil {
+		t.Fatalf("keysetFromJSON from bad JSON succeeded unexpectedly.")
+	}
+}
+
+func TestKeysetToJSONFail(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	ctx := context.Background()
+	if _, err := keysetToJSON(ctx, keysetService, []byte("badKeyset")); err == nil {
+		t.Fatalf("keysetToJSON with bad keyset succeeded unexpectedly.")
+	}
+}
+
+func aeadEncrypt(ctx context.Context, aeadService *services.AEADService, keyset []byte, plaintext []byte, associatedData []byte) ([]byte, error) {
+	encRequest := &pb.AeadEncryptRequest{
+		Keyset:         keyset,
+		Plaintext:      plaintext,
+		AssociatedData: associatedData,
+	}
+	encResponse, err := aeadService.Encrypt(ctx, encRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := encResponse.Result.(type) {
+	case *pb.AeadEncryptResponse_Ciphertext:
+		return r.Ciphertext, nil
+	case *pb.AeadEncryptResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("encResponse.Result has unexpected type %T", r)
+	}
+}
+
+func aeadDecrypt(ctx context.Context, aeadService *services.AEADService, keyset []byte, ciphertext []byte, associatedData []byte) ([]byte, error) {
+	decRequest := &pb.AeadDecryptRequest{
+		Keyset:         keyset,
+		Ciphertext:     ciphertext,
+		AssociatedData: associatedData,
+	}
+	decResponse, err := aeadService.Decrypt(ctx, decRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := decResponse.Result.(type) {
+	case *pb.AeadDecryptResponse_Plaintext:
+		return r.Plaintext, nil
+	case *pb.AeadDecryptResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("encResponse.Result has unexpected type %T", r)
+	}
+}
+
+func TestGenerateEncryptDecrypt(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	aeadService := &services.AEADService{}
+	ctx := context.Background()
+
+	template, err := proto.Marshal(aead.AES128GCMKeyTemplate())
+	if err != nil {
+		t.Fatalf("proto.Marshal(aead.AES128GCMKeyTemplate()) failed: %v", err)
+	}
+
+	keyset, err := genKeyset(ctx, keysetService, template)
+	if err != nil {
+		t.Fatalf("genKeyset failed: %v", err)
+	}
+
+	plaintext := []byte("The quick brown fox jumps over the lazy dog")
+	associatedData := []byte("Associated Data")
+	ciphertext, err := aeadEncrypt(ctx, aeadService, keyset, plaintext, associatedData)
+	if err != nil {
+		t.Fatalf("Aead Encrypt failed: %v", err)
+	}
+	output, err := aeadDecrypt(ctx, aeadService, keyset, ciphertext, associatedData)
+	if err != nil {
+		t.Fatalf("aeadDecrypt failed: %v", err)
+	}
+	if bytes.Compare(output, plaintext) != 0 {
+		t.Fatalf("Decrypted ciphertext is %v, want %v", output, plaintext)
+	}
+
+	if _, err := genKeyset(ctx, keysetService, []byte("badTemplate")); err == nil {
+		t.Fatalf("genKeyset from bad template succeeded unexpectedly.")
+	}
+	if _, err := aeadEncrypt(ctx, aeadService, []byte("badKeyset"), plaintext, associatedData); err == nil {
+		t.Fatalf("aeadEncrypt with bad keyset succeeded unexpectedly.")
+	}
+	if _, err := aeadDecrypt(ctx, aeadService, keyset, []byte("badCiphertext"), associatedData); err == nil {
+		t.Fatalf("aeadDecrypt of bad ciphertext succeeded unexpectedly.")
+	}
+}
+
+func daeadEncrypt(ctx context.Context, daeadService *services.DeterministicAEADService, keyset []byte, plaintext []byte, associatedData []byte) ([]byte, error) {
+	encRequest := &pb.DeterministicAeadEncryptRequest{
+		Keyset:         keyset,
+		Plaintext:      plaintext,
+		AssociatedData: associatedData,
+	}
+	encResponse, err := daeadService.EncryptDeterministically(ctx, encRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := encResponse.Result.(type) {
+	case *pb.DeterministicAeadEncryptResponse_Ciphertext:
+		return r.Ciphertext, nil
+	case *pb.DeterministicAeadEncryptResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("encResponse.Result has unexpected type %T", r)
+	}
+}
+
+func daeadDecrypt(ctx context.Context, daeadService *services.DeterministicAEADService, keyset []byte, ciphertext []byte, associatedData []byte) ([]byte, error) {
+	decRequest := &pb.DeterministicAeadDecryptRequest{
+		Keyset:         keyset,
+		Ciphertext:     ciphertext,
+		AssociatedData: associatedData,
+	}
+	decResponse, err := daeadService.DecryptDeterministically(ctx, decRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := decResponse.Result.(type) {
+	case *pb.DeterministicAeadDecryptResponse_Plaintext:
+		return r.Plaintext, nil
+	case *pb.DeterministicAeadDecryptResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("encResponse.Result has unexpected type %T", r)
+	}
+}
+
+func TestGenerateEncryptDecryptDeterministically(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	daeadService := &services.DeterministicAEADService{}
+	ctx := context.Background()
+
+	template, err := proto.Marshal(daead.AESSIVKeyTemplate())
+	if err != nil {
+		t.Fatalf("proto.Marshal(daead.AESSIVKeyTemplate()) failed: %v", err)
+	}
+
+	keyset, err := genKeyset(ctx, keysetService, template)
+	if err != nil {
+		t.Fatalf("genKeyset failed: %v", err)
+	}
+
+	plaintext := []byte("The quick brown fox jumps over the lazy dog")
+	associatedData := []byte("Associated Data")
+	ciphertext, err := daeadEncrypt(ctx, daeadService, keyset, plaintext, associatedData)
+	if err != nil {
+		t.Fatalf("Aead Encrypt failed: %v", err)
+	}
+	output, err := daeadDecrypt(ctx, daeadService, keyset, ciphertext, associatedData)
+	if err != nil {
+		t.Fatalf("daeadDecrypt failed: %v", err)
+	}
+	if bytes.Compare(output, plaintext) != 0 {
+		t.Fatalf("Decrypted ciphertext is %v, want %v", output, plaintext)
+	}
+
+	if _, err := genKeyset(ctx, keysetService, []byte("badTemplate")); err == nil {
+		t.Fatalf("genKeyset from bad template succeeded unexpectedly.")
+	}
+	if _, err := daeadEncrypt(ctx, daeadService, []byte("badKeyset"), plaintext, associatedData); err == nil {
+		t.Fatalf("daeadEncrypt with bad keyset succeeded unexpectedly.")
+	}
+	if _, err := daeadDecrypt(ctx, daeadService, keyset, []byte("badCiphertext"), associatedData); err == nil {
+		t.Fatalf("daeadDecrypt of bad ciphertext succeeded unexpectedly.")
+	}
+}
+
+func streamingAEADEncrypt(ctx context.Context, streamingAEADService *services.StreamingAEADService, keyset []byte, plaintext []byte, associatedData []byte) ([]byte, error) {
+	encRequest := &pb.StreamingAeadEncryptRequest{
+		Keyset:         keyset,
+		Plaintext:      plaintext,
+		AssociatedData: associatedData,
+	}
+	encResponse, err := streamingAEADService.Encrypt(ctx, encRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := encResponse.Result.(type) {
+	case *pb.StreamingAeadEncryptResponse_Ciphertext:
+		return r.Ciphertext, nil
+	case *pb.StreamingAeadEncryptResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("encResponse.Result has unexpected type %T", r)
+	}
+}
+
+func streamingAEADDecrypt(ctx context.Context, streamingAEADService *services.StreamingAEADService, keyset []byte, ciphertext []byte, associatedData []byte) ([]byte, error) {
+	decRequest := &pb.StreamingAeadDecryptRequest{
+		Keyset:         keyset,
+		Ciphertext:     ciphertext,
+		AssociatedData: associatedData,
+	}
+	decResponse, err := streamingAEADService.Decrypt(ctx, decRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := decResponse.Result.(type) {
+	case *pb.StreamingAeadDecryptResponse_Plaintext:
+		return r.Plaintext, nil
+	case *pb.StreamingAeadDecryptResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("encResponse.Result has unexpected type %T", r)
+	}
+}
+
+func TestGenerateEncryptDecryptStreaming(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	streamingAEADService := &services.StreamingAEADService{}
+	ctx := context.Background()
+
+	template, err := proto.Marshal(streamingaead.AES128GCMHKDF4KBKeyTemplate())
+	if err != nil {
+		t.Fatalf("proto.Marshal(streamingaead.AES128GCMHKDF4KBKeyTemplate()) failed: %v", err)
+	}
+
+	keyset, err := genKeyset(ctx, keysetService, template)
+	if err != nil {
+		t.Fatalf("genKeyset failed: %v", err)
+	}
+
+	plaintext := []byte("The quick brown fox jumps over the lazy dog")
+	associatedData := []byte("Associated Data")
+	ciphertext, err := streamingAEADEncrypt(ctx, streamingAEADService, keyset, plaintext, associatedData)
+	if err != nil {
+		t.Fatalf("streamingAEADEncrypt failed: %v", err)
+	}
+	output, err := streamingAEADDecrypt(ctx, streamingAEADService, keyset, ciphertext, associatedData)
+	if err != nil {
+		t.Fatalf("streamingAEADDecrypt failed: %v", err)
+	}
+	if bytes.Compare(output, plaintext) != 0 {
+		t.Errorf("Decrypted ciphertext is %v, want %v", output, plaintext)
+	}
+
+	if _, err := genKeyset(ctx, keysetService, []byte("badTemplate")); err == nil {
+		t.Fatalf("genKeyset from bad template succeeded unexpectedly.")
+	}
+	if _, err := streamingAEADEncrypt(ctx, streamingAEADService, []byte("badKeyset"), plaintext, associatedData); err == nil {
+		t.Fatalf("streamingAEADEncrypt with bad keyset succeeded unexpectedly.")
+	}
+	if _, err := streamingAEADDecrypt(ctx, streamingAEADService, keyset, []byte("badCiphertext"), associatedData); err == nil {
+		t.Fatalf("streamingAEADDecrypt of bad ciphertext succeeded unexpectedly.")
+	}
+}
+
+func computeMAC(ctx context.Context, macService *services.MacService, keyset []byte, data []byte) ([]byte, error) {
+	encRequest := &pb.ComputeMacRequest{
+		Keyset: keyset,
+		Data:   data,
+	}
+	response, err := macService.ComputeMac(ctx, encRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := response.Result.(type) {
+	case *pb.ComputeMacResponse_MacValue:
+		return r.MacValue, nil
+	case *pb.ComputeMacResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("response.Result has unexpected type %T", r)
+	}
+}
+
+func verifyMAC(ctx context.Context, macService *services.MacService, keyset []byte, macValue []byte, data []byte) error {
+	request := &pb.VerifyMacRequest{
+		Keyset:   keyset,
+		MacValue: macValue,
+		Data:     data,
+	}
+	response, err := macService.VerifyMac(ctx, request)
+	if err != nil {
+		return err
+	}
+	if response.Err != "" {
+		return errors.New(response.Err)
+	}
+	return nil
+}
+
+func TestComputeVerifyMac(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	macService := &services.MacService{}
+	ctx := context.Background()
+
+	template, err := proto.Marshal(mac.HMACSHA256Tag128KeyTemplate())
+	if err != nil {
+		t.Fatalf("proto.Marshal(mac.HMACSHA256Tag128KeyTemplate()) failed: %v", err)
+	}
+
+	keyset, err := genKeyset(ctx, keysetService, template)
+	if err != nil {
+		t.Fatalf("genKeyset failed: %v", err)
+	}
+
+	data := []byte("The quick brown fox jumps over the lazy dog")
+	macValue, err := computeMAC(ctx, macService, keyset, data)
+	if err != nil {
+		t.Fatalf("computeMAC failed: %v", err)
+	}
+	if err := verifyMAC(ctx, macService, keyset, macValue, data); err != nil {
+		t.Fatalf("verifyMAC failed: %v", err)
+	}
+
+	if _, err := computeMAC(ctx, macService, []byte("badKeyset"), data); err == nil {
+		t.Fatalf("computeMAC with bad keyset succeeded unexpectedly.")
+	}
+	if err := verifyMAC(ctx, macService, keyset, []byte("badMacValue"), data); err == nil {
+		t.Fatalf("verifyMAC of bad MAC value succeeded unexpectedly.")
+	}
+}
+
+func hybridEncrypt(ctx context.Context, hybridService *services.HybridService, publicKeyset []byte, plaintext []byte, contextInfo []byte) ([]byte, error) {
+	encRequest := &pb.HybridEncryptRequest{
+		PublicKeyset: publicKeyset,
+		Plaintext:    plaintext,
+		ContextInfo:  contextInfo,
+	}
+	encResponse, err := hybridService.Encrypt(ctx, encRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := encResponse.Result.(type) {
+	case *pb.HybridEncryptResponse_Ciphertext:
+		return r.Ciphertext, nil
+	case *pb.HybridEncryptResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("encResponse.Result has unexpected type %T", r)
+	}
+}
+
+func hybridDecrypt(ctx context.Context, hybridService *services.HybridService, privateKeyset []byte, ciphertext []byte, contextInfo []byte) ([]byte, error) {
+	decRequest := &pb.HybridDecryptRequest{
+		PrivateKeyset: privateKeyset,
+		Ciphertext:    ciphertext,
+		ContextInfo:   contextInfo,
+	}
+	decResponse, err := hybridService.Decrypt(ctx, decRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := decResponse.Result.(type) {
+	case *pb.HybridDecryptResponse_Plaintext:
+		return r.Plaintext, nil
+	case *pb.HybridDecryptResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("decResponse.Result has unexpected type %T", r)
+	}
+}
+
+func TestHybridGenerateEncryptDecrypt(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	hybridService := &services.HybridService{}
+	ctx := context.Background()
+
+	template, err := proto.Marshal(hybrid.ECIESHKDFAES128GCMKeyTemplate())
+	if err != nil {
+		t.Fatalf("proto.Marshal(hybrid.ECIESHKDFAES128GCMKeyTemplate()) failed: %v", err)
+	}
+
+	privateKeyset, err := genKeyset(ctx, keysetService, template)
+	if err != nil {
+		t.Fatalf("genKeyset failed: %v", err)
+	}
+	publicKeyset, err := pubKeyset(ctx, keysetService, privateKeyset)
+	if err != nil {
+		t.Fatalf("pubKeyset failed: %v", err)
+	}
+
+	plaintext := []byte("The quick brown fox jumps over the lazy dog")
+	associatedData := []byte("Associated Data")
+	ciphertext, err := hybridEncrypt(ctx, hybridService, publicKeyset, plaintext, associatedData)
+	if err != nil {
+		t.Fatalf("hybridEncrypt failed: %v", err)
+	}
+	output, err := hybridDecrypt(ctx, hybridService, privateKeyset, ciphertext, associatedData)
+	if err != nil {
+		t.Fatalf("hybridDecrypt failed: %v", err)
+	}
+	if bytes.Compare(output, plaintext) != 0 {
+		t.Fatalf("Decrypted ciphertext is %v, want %v", output, plaintext)
+	}
+
+	if _, err := pubKeyset(ctx, keysetService, []byte("badPrivateKeyset")); err == nil {
+		t.Fatalf("pubKeyset from bad private keyset succeeded unexpectedly.")
+	}
+	if _, err := hybridEncrypt(ctx, hybridService, []byte("badPublicKeyset"), plaintext, associatedData); err == nil {
+		t.Fatalf("hybridEncrypt with bad public keyset succeeded unexpectedly.")
+	}
+	if _, err := hybridDecrypt(ctx, hybridService, []byte("badPrivateKeyset"), ciphertext, associatedData); err == nil {
+		t.Fatalf("hybridDecrypt with bad private keyset succeeded unexpectedly.")
+	}
+	if _, err := hybridDecrypt(ctx, hybridService, privateKeyset, []byte("badCiphertext"), associatedData); err == nil {
+		t.Fatalf("hybridDecrypt of bad ciphertext succeeded unexpectedly.")
+	}
+}
+
+func signatureSign(ctx context.Context, signatureService *services.SignatureService, privateKeyset []byte, data []byte) ([]byte, error) {
+	encRequest := &pb.SignatureSignRequest{
+		PrivateKeyset: privateKeyset,
+		Data:          data,
+	}
+	response, err := signatureService.Sign(ctx, encRequest)
+	if err != nil {
+		return nil, err
+	}
+	switch r := response.Result.(type) {
+	case *pb.SignatureSignResponse_Signature:
+		return r.Signature, nil
+	case *pb.SignatureSignResponse_Err:
+		return nil, errors.New(r.Err)
+	default:
+		return nil, fmt.Errorf("response.Result has unexpected type %T", r)
+	}
+}
+
+func signatureVerify(ctx context.Context, signatureService *services.SignatureService, publicKeyset []byte, signatureValue []byte, data []byte) error {
+	request := &pb.SignatureVerifyRequest{
+		PublicKeyset: publicKeyset,
+		Signature:    signatureValue,
+		Data:         data,
+	}
+	response, err := signatureService.Verify(ctx, request)
+	if err != nil {
+		return err
+	}
+	if response.Err != "" {
+		return errors.New(response.Err)
+	}
+	return nil
+}
+
+func TestSignatureSignVerify(t *testing.T) {
+	keysetService := &services.KeysetService{}
+	signatureService := &services.SignatureService{}
+	ctx := context.Background()
+
+	template, err := proto.Marshal(signature.ECDSAP256KeyTemplate())
+	if err != nil {
+		t.Fatalf("proto.Marshal(signature.ECDSAP256KeyTemplate()) failed: %v", err)
+	}
+
+	privateKeyset, err := genKeyset(ctx, keysetService, template)
+	if err != nil {
+		t.Fatalf("genKeyset failed: %v", err)
+	}
+	publicKeyset, err := pubKeyset(ctx, keysetService, privateKeyset)
+	if err != nil {
+		t.Fatalf("pubKeyset failed: %v", err)
+	}
+
+	data := []byte("The quick brown fox jumps over the lazy dog")
+	signatureValue, err := signatureSign(ctx, signatureService, privateKeyset, data)
+	if err != nil {
+		t.Fatalf("signatureSign failed: %v", err)
+	}
+	if err := signatureVerify(ctx, signatureService, publicKeyset, signatureValue, data); err != nil {
+		t.Fatalf("signatureVerify failed: %v", err)
+	}
+
+	if _, err := signatureSign(ctx, signatureService, []byte("badPrivateKeyset"), data); err == nil {
+		t.Fatalf("signatureSign with bad private keyset succeeded unexpectedly.")
+	}
+	if err := signatureVerify(ctx, signatureService, publicKeyset, []byte("badSignature"), data); err == nil {
+		t.Fatalf("signatureVerify of bad signature succeeded unexpectedly.")
+	}
+	if err := signatureVerify(ctx, signatureService, []byte("badPublicKeyset"), signatureValue, data); err == nil {
+		t.Fatalf("signatureVerify of bad public keyset succeeded unexpectedly.")
+	}
+}
+
+func TestServerInfo(t *testing.T) {
+	metadataService := &services.MetadataService{}
+	ctx := context.Background()
+
+	req := &pb.ServerInfoRequest{}
+	rsp, err := metadataService.GetServerInfo(ctx, req)
+	if err != nil {
+		t.Fatalf("GetServerInfo failed: %v", err)
+	}
+	if strings.Compare(rsp.GetLanguage(), "go") != 0 {
+		t.Fatalf("Expected language 'go', got: %v", rsp.GetLanguage())
+	}
+}
diff --git a/testing/go/signature_service.go b/testing/go/signature_service.go
new file mode 100644
index 0000000..a756343
--- /dev/null
+++ b/testing/go/signature_service.go
@@ -0,0 +1,67 @@
+// 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 services
+
+import (
+	"bytes"
+	"context"
+
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/signature"
+	"github.com/google/tink/go/testkeyset"
+	pb "github.com/google/tink/proto/testing/testing_api_go_grpc"
+)
+
+// SignatureService implements the Signature testing service.
+type SignatureService struct {
+}
+
+func (s *SignatureService) Sign(ctx context.Context, req *pb.SignatureSignRequest) (*pb.SignatureSignResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.PrivateKeyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.SignatureSignResponse{
+			Result: &pb.SignatureSignResponse_Err{err.Error()}}, nil
+	}
+	signer, err := signature.NewSigner(handle)
+	if err != nil {
+		return &pb.SignatureSignResponse{
+			Result: &pb.SignatureSignResponse_Err{err.Error()}}, nil
+	}
+	sigValue, err := signer.Sign(req.Data)
+	if err != nil {
+		return &pb.SignatureSignResponse{
+			Result: &pb.SignatureSignResponse_Err{err.Error()}}, nil
+	}
+	return &pb.SignatureSignResponse{
+		Result: &pb.SignatureSignResponse_Signature{sigValue}}, nil
+}
+
+func (s *SignatureService) Verify(ctx context.Context, req *pb.SignatureVerifyRequest) (*pb.SignatureVerifyResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.PublicKeyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.SignatureVerifyResponse{Err: err.Error()}, nil
+	}
+	verifier, err := signature.NewVerifier(handle)
+	if err != nil {
+		return &pb.SignatureVerifyResponse{Err: err.Error()}, nil
+	}
+	err = verifier.Verify(req.Signature, req.Data)
+	if err != nil {
+		return &pb.SignatureVerifyResponse{Err: err.Error()}, nil
+	}
+	return &pb.SignatureVerifyResponse{}, nil
+}
diff --git a/testing/go/streaming_aead_service.go b/testing/go/streaming_aead_service.go
new file mode 100644
index 0000000..8956c21
--- /dev/null
+++ b/testing/go/streaming_aead_service.go
@@ -0,0 +1,111 @@
+// 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 services
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io"
+
+	"github.com/google/tink/go/keyset"
+	"github.com/google/tink/go/streamingaead"
+	"github.com/google/tink/go/testkeyset"
+	pb "github.com/google/tink/proto/testing/testing_api_go_grpc"
+)
+
+const (
+	decryptChunkSize = 2
+)
+
+// StreamingAEADService implements the StreamingAead testing service.
+type StreamingAEADService struct {
+}
+
+func (s *StreamingAEADService) Encrypt(ctx context.Context, req *pb.StreamingAeadEncryptRequest) (*pb.StreamingAeadEncryptResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.Keyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.StreamingAeadEncryptResponse{
+			Result: &pb.StreamingAeadEncryptResponse_Err{err.Error()}}, nil
+	}
+	cipher, err := streamingaead.New(handle)
+	if err != nil {
+		return &pb.StreamingAeadEncryptResponse{
+			Result: &pb.StreamingAeadEncryptResponse_Err{err.Error()}}, nil
+	}
+	ciphertextBuf := &bytes.Buffer{}
+	w, err := cipher.NewEncryptingWriter(ciphertextBuf, req.AssociatedData)
+	if err != nil {
+		errMsg := fmt.Sprintf("cannot create an encrypt writer: %v", err)
+		return &pb.StreamingAeadEncryptResponse{
+			Result: &pb.StreamingAeadEncryptResponse_Err{errMsg}}, nil
+	}
+	n, err := w.Write(req.Plaintext)
+	if err != nil {
+		errMsg := fmt.Sprintf("error writing to an encrypt writer: %v", err)
+		return &pb.StreamingAeadEncryptResponse{
+			Result: &pb.StreamingAeadEncryptResponse_Err{errMsg}}, nil
+	}
+	if n != len(req.Plaintext) {
+		errMsg := fmt.Sprintf("unexpected number of bytes written. Got=%d;want=%d", n, len(req.Plaintext))
+		return &pb.StreamingAeadEncryptResponse{
+			Result: &pb.StreamingAeadEncryptResponse_Err{errMsg}}, nil
+	}
+	if err := w.Close(); err != nil {
+		errMsg := fmt.Sprintf("error closing writer: %v", err)
+		return &pb.StreamingAeadEncryptResponse{
+			Result: &pb.StreamingAeadEncryptResponse_Err{errMsg}}, nil
+	}
+	return &pb.StreamingAeadEncryptResponse{
+		Result: &pb.StreamingAeadEncryptResponse_Ciphertext{ciphertextBuf.Bytes()}}, nil
+}
+
+func (s *StreamingAEADService) Decrypt(ctx context.Context, req *pb.StreamingAeadDecryptRequest) (*pb.StreamingAeadDecryptResponse, error) {
+	reader := keyset.NewBinaryReader(bytes.NewReader(req.Keyset))
+	handle, err := testkeyset.Read(reader)
+	if err != nil {
+		return &pb.StreamingAeadDecryptResponse{
+			Result: &pb.StreamingAeadDecryptResponse_Err{err.Error()}}, nil
+	}
+	cipher, err := streamingaead.New(handle)
+	if err != nil {
+		return &pb.StreamingAeadDecryptResponse{
+			Result: &pb.StreamingAeadDecryptResponse_Err{err.Error()}}, nil
+	}
+	r, err := cipher.NewDecryptingReader(bytes.NewBuffer(req.Ciphertext), req.AssociatedData)
+	if err != nil {
+		errMsg := fmt.Sprintf("cannot create an encrypt reader: %v", err)
+		return &pb.StreamingAeadDecryptResponse{
+			Result: &pb.StreamingAeadDecryptResponse_Err{errMsg}}, nil
+	}
+	plaintextBuf := &bytes.Buffer{}
+	var (
+		chunk = make([]byte, decryptChunkSize)
+		eof   = false
+	)
+	for !eof {
+		n, err := r.Read(chunk)
+		if err != nil && err != io.EOF {
+			errMsg := fmt.Sprintf("error reading chunk: %v", err)
+			return &pb.StreamingAeadDecryptResponse{
+				Result: &pb.StreamingAeadDecryptResponse_Err{errMsg}}, nil
+		}
+		eof = err == io.EOF
+		plaintextBuf.Write(chunk[:n])
+	}
+	return &pb.StreamingAeadDecryptResponse{
+		Result: &pb.StreamingAeadDecryptResponse_Plaintext{plaintextBuf.Bytes()}}, nil
+}
diff --git a/testing/go/testing_server.go b/testing/go/testing_server.go
new file mode 100644
index 0000000..243c3bd
--- /dev/null
+++ b/testing/go/testing_server.go
@@ -0,0 +1,54 @@
+// 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 main is implements an gRPC server for testing_api.
+package main
+
+import (
+	"fmt"
+	"log"
+	"net"
+
+	"flag"
+	// context is used to cancel outstanding requests
+	"google.golang.org/grpc"
+	pbgrpc "github.com/google/tink/proto/testing/testing_api_go_grpc"
+	"github.com/google/tink/testing/go/services"
+)
+
+var (
+	port = flag.Int("port", 10000, "The server port")
+)
+
+func main() {
+	flag.Parse()
+	lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
+	if err != nil {
+		log.Fatalf("Server failed to listen: %v", err)
+	}
+	log.Printf("Server is now listening on port: %d", *port)
+	server := grpc.NewServer()
+	if err != nil {
+		log.Fatalf("Failed to create new grpcprod server: %v", err)
+	}
+	pbgrpc.RegisterMetadataServer(server, &services.MetadataService{})
+	pbgrpc.RegisterKeysetServer(server, &services.KeysetService{})
+	pbgrpc.RegisterAeadServer(server, &services.AEADService{})
+	pbgrpc.RegisterDeterministicAeadServer(server, &services.DeterministicAEADService{})
+	pbgrpc.RegisterHybridServer(server, &services.HybridService{})
+	pbgrpc.RegisterMacServer(server, &services.MacService{})
+	pbgrpc.RegisterSignatureServer(server, &services.SignatureService{})
+	pbgrpc.RegisterStreamingAeadServer(server, &services.StreamingAEADService{})
+	server.Serve(lis)
+}
diff --git a/testing/java_src/.bazelversion b/testing/java_src/.bazelversion
new file mode 100644
index 0000000..fd2a018
--- /dev/null
+++ b/testing/java_src/.bazelversion
@@ -0,0 +1 @@
+3.1.0
diff --git a/testing/java_src/BUILD.bazel b/testing/java_src/BUILD.bazel
new file mode 100644
index 0000000..31d2263
--- /dev/null
+++ b/testing/java_src/BUILD.bazel
@@ -0,0 +1,121 @@
+load("@tink_java//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+java_proto_library(
+    name = "testing_api_java_proto",
+    testonly = 1,
+    deps = ["@tink_base//proto/testing:testing_api_proto"],
+)
+
+java_grpc_library(
+    name = "testing_api_java_grpc",
+    testonly = 1,
+    srcs = ["@tink_base//proto/testing:testing_api_proto"],
+    deps = [":testing_api_java_proto"],
+)
+
+java_library(
+    name = "testing_services",
+    testonly = 1,
+    srcs = [
+        "java/com/google/crypto/tink/testing/AeadServiceImpl.java",
+        "java/com/google/crypto/tink/testing/DeterministicAeadServiceImpl.java",
+        "java/com/google/crypto/tink/testing/HybridServiceImpl.java",
+        "java/com/google/crypto/tink/testing/KeysetServiceImpl.java",
+        "java/com/google/crypto/tink/testing/MacServiceImpl.java",
+        "java/com/google/crypto/tink/testing/MetadataServiceImpl.java",
+        "java/com/google/crypto/tink/testing/SignatureServiceImpl.java",
+        "java/com/google/crypto/tink/testing/StreamingAeadServiceImpl.java",
+    ],
+    javacopts = JAVACOPTS_OSS,
+    deps = [
+        ":testing_api_java_grpc",
+        ":testing_api_java_proto",
+        "@com_google_protobuf//:protobuf_java",
+        "@io_grpc_grpc_java//api",
+        "@io_grpc_grpc_java//protobuf",
+        "@io_grpc_grpc_java//stub",
+        "@tink_java//:cleartext_keyset_handle",
+        "@tink_java//proto:tink_java_proto",
+        "@tink_java//src/main/java/com/google/crypto/tink:cleartext_keyset_handle",
+        "@tink_java//src/main/java/com/google/crypto/tink:core",
+        "@tink_java//src/main/java/com/google/crypto/tink:primitives",
+    ],
+)
+
+java_binary(
+    name = "testing_server",
+    testonly = 1,
+    srcs = [
+        "java/com/google/crypto/tink/testing/TestingServer.java",
+    ],
+    javacopts = JAVACOPTS_OSS,
+    main_class = "com.google.crypto.tink.testing.TestingServer",
+    runtime_deps = [
+        "@io_grpc_grpc_java//netty",
+    ],
+    deps = [
+        ":testing_services",
+        "@io_grpc_grpc_java//api",
+        "@tink_java//src/main/java/com/google/crypto/tink/config:tink_config",
+    ],
+)
+
+java_test(
+    name = "TestingServicesTest",
+    size = "small",
+    srcs = [
+        "javatests/com/google/crypto/tink/testing/TestingServicesTest.java",
+    ],
+    deps = [
+        ":testing_api_java_grpc",
+        ":testing_api_java_proto",
+        ":testing_services",
+        "@com_google_protobuf//:protobuf_java",
+        "@com_google_protobuf//:protobuf_java_util",
+        "@io_grpc_grpc_java//api",
+        "@io_grpc_grpc_java//core:inprocess",
+        "@io_grpc_grpc_java//protobuf",
+        "@io_grpc_grpc_java//stub",
+        "@io_grpc_grpc_java//testing",
+        "@maven//:com_google_truth_truth",
+        "@maven//:junit_junit",
+        "@tink_java//proto:tink_java_proto",
+        "@tink_java//src/main/java/com/google/crypto/tink:core",
+        "@tink_java//src/main/java/com/google/crypto/tink/aead:aead_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/config:tink_config",
+        "@tink_java//src/main/java/com/google/crypto/tink/daead:deterministic_aead_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/mac:mac_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_key_templates",
+    ],
+)
+
+java_test(
+    name = "AsymmetricTestingServicesTest",
+    size = "small",
+    srcs = [
+        "javatests/com/google/crypto/tink/testing/AsymmetricTestingServicesTest.java",
+    ],
+    deps = [
+        ":testing_api_java_grpc",
+        ":testing_api_java_proto",
+        ":testing_services",
+        "@com_google_protobuf//:protobuf_java",
+        "@com_google_protobuf//:protobuf_java_util",
+        "@io_grpc_grpc_java//api",
+        "@io_grpc_grpc_java//core:inprocess",
+        "@io_grpc_grpc_java//protobuf",
+        "@io_grpc_grpc_java//stub",
+        "@io_grpc_grpc_java//testing",
+        "@maven//:com_google_truth_truth",
+        "@maven//:junit_junit",
+        "@tink_java//proto:tink_java_proto",
+        "@tink_java//src/main/java/com/google/crypto/tink/config:tink_config",
+        "@tink_java//src/main/java/com/google/crypto/tink/hybrid:hybrid_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/signature:signature_key_templates",
+    ],
+)
diff --git a/testing/java_src/WORKSPACE b/testing/java_src/WORKSPACE
new file mode 100644
index 0000000..0f98e36
--- /dev/null
+++ b/testing/java_src/WORKSPACE
@@ -0,0 +1,37 @@
+workspace(name = "testing_java")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+local_repository(
+    name = "tink_base",
+    path = "../..",
+)
+
+local_repository(
+    name = "tink_java",
+    path = "../../java_src",
+)
+
+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()
+
+load("@tink_java//:tink_java_deps.bzl", "tink_java_deps")
+tink_java_deps()
+
+load("@tink_java//:tink_java_deps_init.bzl", "tink_java_deps_init")
+tink_java_deps_init()
+
+http_archive(
+    name = "io_grpc_grpc_java",
+    sha256 = "446ad7a2e85bbd05406dbf95232c7c49ed90de83b3b60cb2048b0c4c9f254d29",
+    strip_prefix = "grpc-java-1.29.0",
+    url = "https://github.com/grpc/grpc-java/archive/v1.29.0.zip",
+)
+
+load("@io_grpc_grpc_java//:repositories.bzl", "grpc_java_repositories")
+
+grpc_java_repositories()
+
diff --git a/testing/java_src/java/com/google/crypto/tink/testing/AeadServiceImpl.java b/testing/java_src/java/com/google/crypto/tink/testing/AeadServiceImpl.java
new file mode 100644
index 0000000..db45012
--- /dev/null
+++ b/testing/java_src/java/com/google/crypto/tink/testing/AeadServiceImpl.java
@@ -0,0 +1,88 @@
+// 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.testing;
+
+import com.google.crypto.tink.Aead;
+import com.google.crypto.tink.BinaryKeysetReader;
+import com.google.crypto.tink.CleartextKeysetHandle;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.proto.testing.AeadDecryptRequest;
+import com.google.crypto.tink.proto.testing.AeadDecryptResponse;
+import com.google.crypto.tink.proto.testing.AeadEncryptRequest;
+import com.google.crypto.tink.proto.testing.AeadEncryptResponse;
+import com.google.crypto.tink.proto.testing.AeadGrpc.AeadImplBase;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/** Implements a gRPC Aead Testing service. */
+public final class AeadServiceImpl extends AeadImplBase {
+
+  public AeadServiceImpl() throws GeneralSecurityException {
+  }
+
+  /** Encrypts a message. */
+  @Override
+  public void encrypt(
+      AeadEncryptRequest request, StreamObserver<AeadEncryptResponse> responseObserver) {
+    AeadEncryptResponse response;
+    try {
+      KeysetHandle keysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getKeyset().toByteArray()));
+      Aead aead = keysetHandle.getPrimitive(Aead.class);
+      byte[] ciphertext =
+          aead.encrypt(
+              request.getPlaintext().toByteArray(), request.getAssociatedData().toByteArray());
+      response =
+          AeadEncryptResponse.newBuilder().setCiphertext(ByteString.copyFrom(ciphertext)).build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e)  {
+      response = AeadEncryptResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  /** Decrypts a message. */
+  @Override
+  public void decrypt(
+      AeadDecryptRequest request, StreamObserver<AeadDecryptResponse> responseObserver) {
+    AeadDecryptResponse response;
+    try {
+      KeysetHandle keysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getKeyset().toByteArray()));
+      Aead aead = keysetHandle.getPrimitive(Aead.class);
+      byte[] plaintext =
+          aead.decrypt(
+              request.getCiphertext().toByteArray(), request.getAssociatedData().toByteArray());
+      response =
+          AeadDecryptResponse.newBuilder().setPlaintext(ByteString.copyFrom(plaintext)).build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e) {
+      response = AeadDecryptResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+}
diff --git a/testing/java_src/java/com/google/crypto/tink/testing/DeterministicAeadServiceImpl.java b/testing/java_src/java/com/google/crypto/tink/testing/DeterministicAeadServiceImpl.java
new file mode 100644
index 0000000..249b0eb
--- /dev/null
+++ b/testing/java_src/java/com/google/crypto/tink/testing/DeterministicAeadServiceImpl.java
@@ -0,0 +1,94 @@
+// 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.testing;
+
+import com.google.crypto.tink.BinaryKeysetReader;
+import com.google.crypto.tink.CleartextKeysetHandle;
+import com.google.crypto.tink.DeterministicAead;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.proto.testing.DeterministicAeadDecryptRequest;
+import com.google.crypto.tink.proto.testing.DeterministicAeadDecryptResponse;
+import com.google.crypto.tink.proto.testing.DeterministicAeadEncryptRequest;
+import com.google.crypto.tink.proto.testing.DeterministicAeadEncryptResponse;
+import com.google.crypto.tink.proto.testing.DeterministicAeadGrpc.DeterministicAeadImplBase;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/** Implements a gRPC DeterministicAead Testing service. */
+public final class DeterministicAeadServiceImpl extends DeterministicAeadImplBase {
+
+  public DeterministicAeadServiceImpl() throws GeneralSecurityException {
+  }
+
+  /** Encrypts a message. */
+  @Override
+  public void encryptDeterministically(
+      DeterministicAeadEncryptRequest request,
+      StreamObserver<DeterministicAeadEncryptResponse> responseObserver) {
+    DeterministicAeadEncryptResponse response;
+    try {
+      KeysetHandle keysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getKeyset().toByteArray()));
+      DeterministicAead daead = keysetHandle.getPrimitive(DeterministicAead.class);
+      byte[] ciphertext =
+          daead.encryptDeterministically(
+              request.getPlaintext().toByteArray(), request.getAssociatedData().toByteArray());
+      response =
+          DeterministicAeadEncryptResponse.newBuilder()
+              .setCiphertext(ByteString.copyFrom(ciphertext))
+              .build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e)  {
+      response = DeterministicAeadEncryptResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  /** Decrypts a message. */
+  @Override
+  public void decryptDeterministically(
+      DeterministicAeadDecryptRequest request,
+      StreamObserver<DeterministicAeadDecryptResponse> responseObserver) {
+    DeterministicAeadDecryptResponse response;
+    try {
+      KeysetHandle keysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getKeyset().toByteArray()));
+      DeterministicAead daead = keysetHandle.getPrimitive(DeterministicAead.class);
+      byte[] plaintext =
+          daead.decryptDeterministically(
+              request.getCiphertext().toByteArray(), request.getAssociatedData().toByteArray());
+      response =
+          DeterministicAeadDecryptResponse.newBuilder()
+              .setPlaintext(ByteString.copyFrom(plaintext))
+              .build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e) {
+      response = DeterministicAeadDecryptResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+}
diff --git a/testing/java_src/java/com/google/crypto/tink/testing/HybridServiceImpl.java b/testing/java_src/java/com/google/crypto/tink/testing/HybridServiceImpl.java
new file mode 100644
index 0000000..0952bf3
--- /dev/null
+++ b/testing/java_src/java/com/google/crypto/tink/testing/HybridServiceImpl.java
@@ -0,0 +1,89 @@
+// 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.testing;
+
+import com.google.crypto.tink.BinaryKeysetReader;
+import com.google.crypto.tink.CleartextKeysetHandle;
+import com.google.crypto.tink.HybridDecrypt;
+import com.google.crypto.tink.HybridEncrypt;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.proto.testing.HybridDecryptRequest;
+import com.google.crypto.tink.proto.testing.HybridDecryptResponse;
+import com.google.crypto.tink.proto.testing.HybridEncryptRequest;
+import com.google.crypto.tink.proto.testing.HybridEncryptResponse;
+import com.google.crypto.tink.proto.testing.HybridGrpc.HybridImplBase;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/** Implements a gRPC Hybrid Encryption Testing service. */
+public final class HybridServiceImpl extends HybridImplBase {
+
+  public HybridServiceImpl() throws GeneralSecurityException {
+  }
+
+  /** Encrypts a message. */
+  @Override
+  public void encrypt(
+      HybridEncryptRequest request, StreamObserver<HybridEncryptResponse> responseObserver) {
+    HybridEncryptResponse response;
+    try {
+      KeysetHandle publicKeysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getPublicKeyset().toByteArray()));
+      HybridEncrypt hybridEncrypt = publicKeysetHandle.getPrimitive(HybridEncrypt.class);
+      byte[] ciphertext =
+          hybridEncrypt.encrypt(
+              request.getPlaintext().toByteArray(), request.getContextInfo().toByteArray());
+      response =
+          HybridEncryptResponse.newBuilder().setCiphertext(ByteString.copyFrom(ciphertext)).build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e)  {
+      response = HybridEncryptResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  /** Decrypts a message. */
+  @Override
+  public void decrypt(
+      HybridDecryptRequest request, StreamObserver<HybridDecryptResponse> responseObserver) {
+    HybridDecryptResponse response;
+    try {
+      KeysetHandle privateKeysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getPrivateKeyset().toByteArray()));
+      HybridDecrypt hybridDecrypt = privateKeysetHandle.getPrimitive(HybridDecrypt.class);
+      byte[] plaintext =
+          hybridDecrypt.decrypt(
+              request.getCiphertext().toByteArray(), request.getContextInfo().toByteArray());
+      response =
+          HybridDecryptResponse.newBuilder().setPlaintext(ByteString.copyFrom(plaintext)).build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e) {
+      response = HybridDecryptResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+}
diff --git a/testing/java_src/java/com/google/crypto/tink/testing/KeysetServiceImpl.java b/testing/java_src/java/com/google/crypto/tink/testing/KeysetServiceImpl.java
new file mode 100644
index 0000000..86c126f
--- /dev/null
+++ b/testing/java_src/java/com/google/crypto/tink/testing/KeysetServiceImpl.java
@@ -0,0 +1,173 @@
+// 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.testing;
+
+import com.google.crypto.tink.BinaryKeysetReader;
+import com.google.crypto.tink.BinaryKeysetWriter;
+import com.google.crypto.tink.CleartextKeysetHandle;
+import com.google.crypto.tink.JsonKeysetReader;
+import com.google.crypto.tink.JsonKeysetWriter;
+import com.google.crypto.tink.KeyTemplate;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.proto.Keyset;
+import com.google.crypto.tink.proto.OutputPrefixType;
+import com.google.crypto.tink.proto.testing.KeysetFromJsonRequest;
+import com.google.crypto.tink.proto.testing.KeysetFromJsonResponse;
+import com.google.crypto.tink.proto.testing.KeysetGenerateRequest;
+import com.google.crypto.tink.proto.testing.KeysetGenerateResponse;
+import com.google.crypto.tink.proto.testing.KeysetGrpc.KeysetImplBase;
+import com.google.crypto.tink.proto.testing.KeysetPublicRequest;
+import com.google.crypto.tink.proto.testing.KeysetPublicResponse;
+import com.google.crypto.tink.proto.testing.KeysetToJsonRequest;
+import com.google.crypto.tink.proto.testing.KeysetToJsonResponse;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.ExtensionRegistryLite;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/** Implement a gRPC Keyset Testing service. */
+public final class KeysetServiceImpl extends KeysetImplBase {
+
+  public KeysetServiceImpl() throws GeneralSecurityException {
+  }
+
+  @Override
+  public void generate(
+      KeysetGenerateRequest request, StreamObserver<KeysetGenerateResponse> responseObserver) {
+    KeysetGenerateResponse response;
+    try {
+      com.google.crypto.tink.proto.KeyTemplate protoTemplate =
+          com.google.crypto.tink.proto.KeyTemplate.parseFrom(
+              request.getTemplate(), ExtensionRegistryLite.getEmptyRegistry());
+      KeyTemplate template =
+          KeyTemplate.create(
+              protoTemplate.getTypeUrl(),
+              protoTemplate.getValue().toByteArray(),
+              convertOutputPrefixTypeFromProto(protoTemplate.getOutputPrefixType()));
+      KeysetHandle keysetHandle = KeysetHandle.generateNew(template);
+      Keyset keyset = CleartextKeysetHandle.getKeyset(keysetHandle);
+      ByteArrayOutputStream keysetStream = new ByteArrayOutputStream();
+      BinaryKeysetWriter.withOutputStream(keysetStream).write(keyset);
+      keysetStream.close();
+      response =
+          KeysetGenerateResponse.newBuilder()
+              .setKeyset(ByteString.copyFrom(keysetStream.toByteArray()))
+              .build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e) {
+      response = KeysetGenerateResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void public_(
+      KeysetPublicRequest request, StreamObserver<KeysetPublicResponse> responseObserver) {
+    KeysetPublicResponse response;
+    try {
+      KeysetHandle privateKeysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getPrivateKeyset().toByteArray()));
+      KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();
+      Keyset publicKeyset = CleartextKeysetHandle.getKeyset(publicKeysetHandle);
+      ByteArrayOutputStream publicKeysetStream = new ByteArrayOutputStream();
+      BinaryKeysetWriter.withOutputStream(publicKeysetStream).write(publicKeyset);
+      publicKeysetStream.close();
+      response =
+          KeysetPublicResponse.newBuilder()
+              .setPublicKeyset(ByteString.copyFrom(publicKeysetStream.toByteArray()))
+              .build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e)  {
+      response = KeysetPublicResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void toJson(
+      KeysetToJsonRequest request, StreamObserver<KeysetToJsonResponse> responseObserver) {
+    KeysetToJsonResponse response;
+    try {
+      KeysetHandle keysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getKeyset().toByteArray()));
+      Keyset keyset = CleartextKeysetHandle.getKeyset(keysetHandle);
+      ByteArrayOutputStream jsonKeysetStream = new ByteArrayOutputStream();
+      JsonKeysetWriter.withOutputStream(jsonKeysetStream).write(keyset);
+      jsonKeysetStream.close();
+      response =
+          KeysetToJsonResponse.newBuilder().setJsonKeyset(jsonKeysetStream.toString()).build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e) {
+      response = KeysetToJsonResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  @Override
+  public void fromJson(
+      KeysetFromJsonRequest request, StreamObserver<KeysetFromJsonResponse> responseObserver) {
+    KeysetFromJsonResponse response;
+    try {
+      KeysetHandle keysetHandle =
+          CleartextKeysetHandle.read(JsonKeysetReader.withString(request.getJsonKeyset()));
+      Keyset keyset = CleartextKeysetHandle.getKeyset(keysetHandle);
+      ByteArrayOutputStream keysetStream = new ByteArrayOutputStream();
+      BinaryKeysetWriter.withOutputStream(keysetStream).write(keyset);
+      keysetStream.close();
+      response =
+          KeysetFromJsonResponse.newBuilder()
+              .setKeyset(ByteString.copyFrom(keysetStream.toByteArray()))
+              .build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e) {
+      response = KeysetFromJsonResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  private static KeyTemplate.OutputPrefixType convertOutputPrefixTypeFromProto(
+      OutputPrefixType outputPrefixType) {
+    switch (outputPrefixType) {
+      case TINK:
+        return KeyTemplate.OutputPrefixType.TINK;
+      case LEGACY:
+        return KeyTemplate.OutputPrefixType.LEGACY;
+      case RAW:
+        return KeyTemplate.OutputPrefixType.RAW;
+      case CRUNCHY:
+        return KeyTemplate.OutputPrefixType.CRUNCHY;
+      default:
+        throw new IllegalArgumentException("Unknown output prefix type");
+    }
+  }
+}
diff --git a/testing/java_src/java/com/google/crypto/tink/testing/MacServiceImpl.java b/testing/java_src/java/com/google/crypto/tink/testing/MacServiceImpl.java
new file mode 100644
index 0000000..2892cb8
--- /dev/null
+++ b/testing/java_src/java/com/google/crypto/tink/testing/MacServiceImpl.java
@@ -0,0 +1,84 @@
+// 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.testing;
+
+import com.google.crypto.tink.BinaryKeysetReader;
+import com.google.crypto.tink.CleartextKeysetHandle;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.Mac;
+import com.google.crypto.tink.proto.testing.ComputeMacRequest;
+import com.google.crypto.tink.proto.testing.ComputeMacResponse;
+import com.google.crypto.tink.proto.testing.MacGrpc.MacImplBase;
+import com.google.crypto.tink.proto.testing.VerifyMacRequest;
+import com.google.crypto.tink.proto.testing.VerifyMacResponse;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/** Implements a gRPC MAC Testing service. */
+public final class MacServiceImpl extends MacImplBase {
+
+  public MacServiceImpl() throws GeneralSecurityException {
+  }
+
+  /** Encrypts a message. */
+  @Override
+  public void computeMac(
+      ComputeMacRequest request,
+      StreamObserver<ComputeMacResponse> responseObserver) {
+    ComputeMacResponse response;
+    try {
+      KeysetHandle keysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getKeyset().toByteArray()));
+      Mac mac = keysetHandle.getPrimitive(Mac.class);
+      byte[] macValue = mac.computeMac(request.getData().toByteArray());
+      response = ComputeMacResponse.newBuilder().setMacValue(ByteString.copyFrom(macValue)).build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e)  {
+      response = ComputeMacResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  /** Decrypts a message. */
+  @Override
+  public void verifyMac(
+      VerifyMacRequest request,
+      StreamObserver<VerifyMacResponse> responseObserver) {
+    VerifyMacResponse response;
+    try {
+      KeysetHandle keysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getKeyset().toByteArray()));
+      Mac mac = keysetHandle.getPrimitive(Mac.class);
+      mac.verifyMac(request.getMacValue().toByteArray(), request.getData().toByteArray());
+      response = VerifyMacResponse.getDefaultInstance();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e) {
+      response = VerifyMacResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+}
diff --git a/testing/java_src/java/com/google/crypto/tink/testing/MetadataServiceImpl.java b/testing/java_src/java/com/google/crypto/tink/testing/MetadataServiceImpl.java
new file mode 100644
index 0000000..b1a74bd
--- /dev/null
+++ b/testing/java_src/java/com/google/crypto/tink/testing/MetadataServiceImpl.java
@@ -0,0 +1,41 @@
+// 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.testing;
+
+import com.google.crypto.tink.Version;
+import com.google.crypto.tink.proto.testing.MetadataGrpc.MetadataImplBase;
+import com.google.crypto.tink.proto.testing.ServerInfoRequest;
+import com.google.crypto.tink.proto.testing.ServerInfoResponse;
+import io.grpc.stub.StreamObserver;
+import java.security.GeneralSecurityException;
+
+/** Implement a gRPC service for the server's metadata. */
+public final class MetadataServiceImpl extends MetadataImplBase {
+
+  public MetadataServiceImpl() throws GeneralSecurityException {
+  }
+
+  @Override
+  public void getServerInfo(
+      ServerInfoRequest request, StreamObserver<ServerInfoResponse> responseObserver) {
+    ServerInfoResponse response =
+        ServerInfoResponse.newBuilder()
+            .setLanguage("java")
+            .setTinkVersion(Version.TINK_VERSION)
+            .build();
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+}
diff --git a/testing/java_src/java/com/google/crypto/tink/testing/SignatureServiceImpl.java b/testing/java_src/java/com/google/crypto/tink/testing/SignatureServiceImpl.java
new file mode 100644
index 0000000..976577d
--- /dev/null
+++ b/testing/java_src/java/com/google/crypto/tink/testing/SignatureServiceImpl.java
@@ -0,0 +1,85 @@
+// 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.testing;
+
+import com.google.crypto.tink.BinaryKeysetReader;
+import com.google.crypto.tink.CleartextKeysetHandle;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.PublicKeySign;
+import com.google.crypto.tink.PublicKeyVerify;
+import com.google.crypto.tink.proto.testing.SignatureGrpc.SignatureImplBase;
+import com.google.crypto.tink.proto.testing.SignatureSignRequest;
+import com.google.crypto.tink.proto.testing.SignatureSignResponse;
+import com.google.crypto.tink.proto.testing.SignatureVerifyRequest;
+import com.google.crypto.tink.proto.testing.SignatureVerifyResponse;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/** Implements a gRPC Signature Testing service. */
+public final class SignatureServiceImpl extends SignatureImplBase {
+
+  public SignatureServiceImpl() throws GeneralSecurityException {
+  }
+
+  /** Signs a message. */
+  @Override
+  public void sign(
+      SignatureSignRequest request,
+      StreamObserver<SignatureSignResponse> responseObserver) {
+    SignatureSignResponse response;
+    try {
+      KeysetHandle privateKeysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getPrivateKeyset().toByteArray()));
+      PublicKeySign signer = privateKeysetHandle.getPrimitive(PublicKeySign.class);
+      byte[] signatureValue = signer.sign(request.getData().toByteArray());
+      response = SignatureSignResponse.newBuilder().setSignature(ByteString.copyFrom(signatureValue)).build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e)  {
+      response = SignatureSignResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  /** Verifies a signature. */
+  @Override
+  public void verify(
+      SignatureVerifyRequest request,
+      StreamObserver<SignatureVerifyResponse> responseObserver) {
+    SignatureVerifyResponse response;
+    try {
+      KeysetHandle publicKeysetHandle =
+          CleartextKeysetHandle.read(
+              BinaryKeysetReader.withBytes(request.getPublicKeyset().toByteArray()));
+      PublicKeyVerify verifier = publicKeysetHandle.getPrimitive(PublicKeyVerify.class);
+      verifier.verify(request.getSignature().toByteArray(), request.getData().toByteArray());
+      response = SignatureVerifyResponse.getDefaultInstance();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e) {
+      response = SignatureVerifyResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+}
diff --git a/testing/java_src/java/com/google/crypto/tink/testing/StreamingAeadServiceImpl.java b/testing/java_src/java/com/google/crypto/tink/testing/StreamingAeadServiceImpl.java
new file mode 100644
index 0000000..68ae63d
--- /dev/null
+++ b/testing/java_src/java/com/google/crypto/tink/testing/StreamingAeadServiceImpl.java
@@ -0,0 +1,107 @@
+// 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.testing;
+
+import com.google.crypto.tink.BinaryKeysetReader;
+import com.google.crypto.tink.CleartextKeysetHandle;
+import com.google.crypto.tink.KeysetHandle;
+import com.google.crypto.tink.StreamingAead;
+import com.google.crypto.tink.proto.testing.StreamingAeadDecryptRequest;
+import com.google.crypto.tink.proto.testing.StreamingAeadDecryptResponse;
+import com.google.crypto.tink.proto.testing.StreamingAeadEncryptRequest;
+import com.google.crypto.tink.proto.testing.StreamingAeadEncryptResponse;
+import com.google.crypto.tink.proto.testing.StreamingAeadGrpc.StreamingAeadImplBase;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.grpc.Status;
+import io.grpc.stub.StreamObserver;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+
+/** Implements a gRPC StreamingAead Testing service. */
+public final class StreamingAeadServiceImpl extends StreamingAeadImplBase {
+
+  public StreamingAeadServiceImpl() throws GeneralSecurityException {
+  }
+
+  /** Encrypts a message. */
+  @Override
+  public void encrypt(
+      StreamingAeadEncryptRequest request,
+      StreamObserver<StreamingAeadEncryptResponse> responseObserver) {
+    StreamingAeadEncryptResponse response;
+    try {
+      KeysetHandle keysetHandle = CleartextKeysetHandle.read(
+          BinaryKeysetReader.withBytes(request.getKeyset().toByteArray()));
+      StreamingAead streamingAead = keysetHandle.getPrimitive(StreamingAead.class);
+
+      ByteArrayOutputStream ciphertextStream = new ByteArrayOutputStream();
+      try (OutputStream encryptingStream =
+          streamingAead.newEncryptingStream(
+              ciphertextStream, request.getAssociatedData().toByteArray())) {
+        request.getPlaintext().writeTo(encryptingStream);
+      }
+      response =
+          StreamingAeadEncryptResponse.newBuilder()
+              .setCiphertext(ByteString.copyFrom(ciphertextStream.toByteArray()))
+              .build();
+
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e)  {
+      response = StreamingAeadEncryptResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).asException());
+      return;
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+
+  /** Decrypts a message. */
+  @Override
+  public void decrypt(
+      StreamingAeadDecryptRequest request,
+      StreamObserver<StreamingAeadDecryptResponse> responseObserver) {
+    StreamingAeadDecryptResponse response;
+    try {
+      KeysetHandle keysetHandle = CleartextKeysetHandle.read(
+          BinaryKeysetReader.withBytes(request.getKeyset().toByteArray()));
+      StreamingAead streamingAead = keysetHandle.getPrimitive(StreamingAead.class);
+
+      InputStream ciphertextStream = request.getCiphertext().newInput();
+      InputStream decryptingStream = streamingAead.newDecryptingStream(
+          ciphertextStream, request.getAssociatedData().toByteArray());
+      ByteArrayOutputStream plaintextStream = new ByteArrayOutputStream();
+      while (true) {
+        int bytesRead = decryptingStream.read();
+        if (bytesRead == -1) {
+          break;
+        }
+        plaintextStream.write(bytesRead);
+      }
+
+      response = StreamingAeadDecryptResponse.newBuilder().setPlaintext(
+          ByteString.copyFrom(plaintextStream.toByteArray())).build();
+    } catch (GeneralSecurityException | InvalidProtocolBufferException e) {
+      response = StreamingAeadDecryptResponse.newBuilder().setErr(e.toString()).build();
+    } catch (IOException e) {
+      response = StreamingAeadDecryptResponse.newBuilder().setErr(e.toString()).build();
+    }
+    responseObserver.onNext(response);
+    responseObserver.onCompleted();
+  }
+}
diff --git a/testing/java_src/java/com/google/crypto/tink/testing/TestingServer.java b/testing/java_src/java/com/google/crypto/tink/testing/TestingServer.java
new file mode 100644
index 0000000..4e3e667
--- /dev/null
+++ b/testing/java_src/java/com/google/crypto/tink/testing/TestingServer.java
@@ -0,0 +1,58 @@
+// 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.testing;
+
+
+
+import com.google.crypto.tink.config.TinkConfig;
+import io.grpc.ServerBuilder;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+/**
+ * Starts a server with Tink testing services.
+ */
+public final class TestingServer {
+
+  private TestingServer() {
+    // no instances
+  }
+
+  public static void main(String[] args)
+      throws InterruptedException, GeneralSecurityException, IOException {
+
+    if ((args.length != 2) || !args[0].equals("--port")) {
+      System.out.println("Usage: TestingServer --port <port>");
+      System.exit(1);
+    }
+    int port = Integer.parseInt(args[1]);
+
+    TinkConfig.register();
+
+    System.out.println("Start server on port " + port);
+    ServerBuilder.forPort(port)
+        .addService(new MetadataServiceImpl())
+        .addService(new KeysetServiceImpl())
+        .addService(new AeadServiceImpl())
+        .addService(new DeterministicAeadServiceImpl())
+        .addService(new StreamingAeadServiceImpl())
+        .addService(new HybridServiceImpl())
+        .addService(new MacServiceImpl())
+        .addService(new SignatureServiceImpl())
+        .build()
+        .start()
+        .awaitTermination();
+  }
+}
diff --git a/testing/java_src/javatests/com/google/crypto/tink/testing/AsymmetricTestingServicesTest.java b/testing/java_src/javatests/com/google/crypto/tink/testing/AsymmetricTestingServicesTest.java
new file mode 100644
index 0000000..c7dab0b
--- /dev/null
+++ b/testing/java_src/javatests/com/google/crypto/tink/testing/AsymmetricTestingServicesTest.java
@@ -0,0 +1,310 @@
+// 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.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.crypto.tink.config.TinkConfig;
+import com.google.crypto.tink.hybrid.HybridKeyTemplates;
+import com.google.crypto.tink.proto.testing.HybridDecryptRequest;
+import com.google.crypto.tink.proto.testing.HybridDecryptResponse;
+import com.google.crypto.tink.proto.testing.HybridEncryptRequest;
+import com.google.crypto.tink.proto.testing.HybridEncryptResponse;
+import com.google.crypto.tink.proto.testing.HybridGrpc;
+import com.google.crypto.tink.proto.testing.KeysetGenerateRequest;
+import com.google.crypto.tink.proto.testing.KeysetGenerateResponse;
+import com.google.crypto.tink.proto.testing.KeysetGrpc;
+import com.google.crypto.tink.proto.testing.KeysetPublicRequest;
+import com.google.crypto.tink.proto.testing.KeysetPublicResponse;
+import com.google.crypto.tink.proto.testing.SignatureGrpc;
+import com.google.crypto.tink.proto.testing.SignatureSignRequest;
+import com.google.crypto.tink.proto.testing.SignatureSignResponse;
+import com.google.crypto.tink.proto.testing.SignatureVerifyRequest;
+import com.google.crypto.tink.proto.testing.SignatureVerifyResponse;
+import com.google.crypto.tink.signature.SignatureKeyTemplates;
+import com.google.protobuf.ByteString;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class AsymmetricTestingServicesTest {
+  private Server server;
+  private ManagedChannel channel;
+  KeysetGrpc.KeysetBlockingStub keysetStub;
+  HybridGrpc.HybridBlockingStub hybridStub;
+  SignatureGrpc.SignatureBlockingStub signatureStub;
+
+
+  @Before
+  public void setUp() throws Exception {
+    TinkConfig.register();
+    String serverName = InProcessServerBuilder.generateName();
+    server = InProcessServerBuilder
+        .forName(serverName)
+        .directExecutor()
+        .addService(new KeysetServiceImpl())
+        .addService(new HybridServiceImpl())
+        .addService(new SignatureServiceImpl())
+        .build()
+        .start();
+    channel = InProcessChannelBuilder
+        .forName(serverName)
+        .directExecutor()
+        .build();
+    keysetStub = KeysetGrpc.newBlockingStub(channel);
+    hybridStub = HybridGrpc.newBlockingStub(channel);
+    signatureStub = SignatureGrpc.newBlockingStub(channel);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    assertThat(channel.shutdown().awaitTermination(5, SECONDS)).isTrue();
+    assertThat(server.shutdown().awaitTermination(5, SECONDS)).isTrue();
+  }
+
+  private static KeysetGenerateResponse generateKeyset(
+      KeysetGrpc.KeysetBlockingStub keysetStub, byte[] template) {
+    KeysetGenerateRequest genRequest =
+        KeysetGenerateRequest.newBuilder().setTemplate(ByteString.copyFrom(template)).build();
+    return keysetStub.generate(genRequest);
+  }
+
+  private static KeysetPublicResponse publicKeyset(
+      KeysetGrpc.KeysetBlockingStub keysetStub, byte[] privateKeyset) {
+    KeysetPublicRequest request =
+        KeysetPublicRequest.newBuilder()
+            .setPrivateKeyset(ByteString.copyFrom(privateKeyset))
+            .build();
+    return keysetStub.public_(request);
+  }
+
+  private static HybridEncryptResponse hybridEncrypt(
+      HybridGrpc.HybridBlockingStub hybridStub,
+      byte[] publicKeyset,
+      byte[] plaintext,
+      byte[] contextInfo) {
+    HybridEncryptRequest encRequest =
+        HybridEncryptRequest.newBuilder()
+            .setPublicKeyset(ByteString.copyFrom(publicKeyset))
+            .setPlaintext(ByteString.copyFrom(plaintext))
+            .setContextInfo(ByteString.copyFrom(contextInfo))
+            .build();
+    return hybridStub.encrypt(encRequest);
+  }
+
+  private static HybridDecryptResponse hybridDecrypt(
+      HybridGrpc.HybridBlockingStub hybridStub,
+      byte[] privateKeyset,
+      byte[] ciphertext,
+      byte[] contextInfo) {
+    HybridDecryptRequest decRequest =
+        HybridDecryptRequest.newBuilder()
+            .setPrivateKeyset(ByteString.copyFrom(privateKeyset))
+            .setCiphertext(ByteString.copyFrom(ciphertext))
+            .setContextInfo(ByteString.copyFrom(contextInfo))
+            .build();
+    return hybridStub.decrypt(decRequest);
+  }
+
+  @Test
+  public void hybridGenerateEncryptDecrypt_success() throws Exception {
+    byte[] template = HybridKeyTemplates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM.toByteArray();
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "generate_encrypt_decrypt".getBytes(UTF_8);
+
+    KeysetGenerateResponse genResponse = generateKeyset(keysetStub, template);
+    assertThat(genResponse.getErr()).isEmpty();
+    byte[] privateKeyset = genResponse.getKeyset().toByteArray();
+
+    KeysetPublicResponse pubResponse = publicKeyset(keysetStub, privateKeyset);
+    assertThat(pubResponse.getErr()).isEmpty();
+    byte[] publicKeyset = pubResponse.getPublicKeyset().toByteArray();
+
+    HybridEncryptResponse encResponse =
+        hybridEncrypt(hybridStub, publicKeyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isEmpty();
+    byte[] ciphertext = encResponse.getCiphertext().toByteArray();
+
+    HybridDecryptResponse decResponse =
+        hybridDecrypt(hybridStub, privateKeyset, ciphertext, associatedData);
+    assertThat(decResponse.getErr()).isEmpty();
+    byte[] output = decResponse.getPlaintext().toByteArray();
+
+    assertThat(output).isEqualTo(plaintext);
+  }
+
+  @Test
+  public void publicKeyset_failsOnBadKeyset() throws Exception {
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    KeysetPublicResponse response = publicKeyset(keysetStub, badKeyset);
+    assertThat(response.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void hybridEncrypt_failsOnBadKeyset() throws Exception {
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] contextInfo = "hybrid_encrypt_bad_keyset".getBytes(UTF_8);
+    HybridEncryptResponse encResponse =
+        hybridEncrypt(hybridStub, badKeyset, plaintext, contextInfo);
+    assertThat(encResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void hybridDecrypt_failsOnBadCiphertext() throws Exception {
+    byte[] template = HybridKeyTemplates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM.toByteArray();
+    byte[] badCiphertext = "bad ciphertext".getBytes(UTF_8);
+    byte[] contextInfo = "hybrid_decrypt_bad_ciphertext".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] privateKeyset = keysetResponse.getKeyset().toByteArray();
+
+    KeysetPublicResponse pubResponse = publicKeyset(keysetStub, privateKeyset);
+    assertThat(pubResponse.getErr()).isEmpty();
+    byte[] publicKeyset = pubResponse.getPublicKeyset().toByteArray();
+
+    HybridDecryptResponse decResponse =
+        hybridDecrypt(hybridStub, publicKeyset, badCiphertext, contextInfo);
+    assertThat(decResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void hybridDecrypt_failsOnBadKeyset() throws Exception {
+    byte[] template = HybridKeyTemplates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM.toByteArray();
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] contextInfo = "hybrid_decrypt_bad_keyset".getBytes(UTF_8);
+
+    KeysetGenerateResponse privateKeysetResponse = generateKeyset(keysetStub, template);
+    assertThat(privateKeysetResponse.getErr()).isEmpty();
+    byte[] privateKeyset = privateKeysetResponse.getKeyset().toByteArray();
+
+    KeysetPublicResponse pubResponse = publicKeyset(keysetStub, privateKeyset);
+    assertThat(pubResponse.getErr()).isEmpty();
+    byte[] publicKeyset = pubResponse.getPublicKeyset().toByteArray();
+
+    HybridEncryptResponse encResponse =
+        hybridEncrypt(hybridStub, publicKeyset, plaintext, contextInfo);
+    assertThat(encResponse.getErr()).isEmpty();
+    byte[] ciphertext = encResponse.getCiphertext().toByteArray();
+
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    HybridDecryptResponse decResponse =
+        hybridDecrypt(hybridStub, badKeyset, ciphertext, contextInfo);
+    assertThat(decResponse.getErr()).isNotEmpty();
+  }
+
+  private static SignatureSignResponse signatureSign(
+      SignatureGrpc.SignatureBlockingStub signatureStub, byte[] privateKeyset, byte[] data) {
+    SignatureSignRequest request =
+        SignatureSignRequest.newBuilder()
+            .setPrivateKeyset(ByteString.copyFrom(privateKeyset))
+            .setData(ByteString.copyFrom(data))
+            .build();
+    return signatureStub.sign(request);
+  }
+
+  private static SignatureVerifyResponse signatureVerify(
+      SignatureGrpc.SignatureBlockingStub signatureStub,
+      byte[] publicKeyset,
+      byte[] signature,
+      byte[] data) {
+    SignatureVerifyRequest request =
+        SignatureVerifyRequest.newBuilder()
+            .setPublicKeyset(ByteString.copyFrom(publicKeyset))
+            .setSignature(ByteString.copyFrom(signature))
+            .setData(ByteString.copyFrom(data))
+            .build();
+    return signatureStub.verify(request);
+  }
+
+  @Test
+  public void signatureSignVerify_success() throws Exception {
+    byte[] template = SignatureKeyTemplates.ECDSA_P256.toByteArray();
+    byte[] data = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+
+    KeysetGenerateResponse genResponse = generateKeyset(keysetStub, template);
+    assertThat(genResponse.getErr()).isEmpty();
+    byte[] privateKeyset = genResponse.getKeyset().toByteArray();
+
+    KeysetPublicResponse pubResponse = publicKeyset(keysetStub, privateKeyset);
+    assertThat(pubResponse.getErr()).isEmpty();
+    byte[] publicKeyset = pubResponse.getPublicKeyset().toByteArray();
+
+    SignatureSignResponse signResponse = signatureSign(signatureStub, privateKeyset, data);
+    assertThat(signResponse.getErr()).isEmpty();
+    byte[] signature = signResponse.getSignature().toByteArray();
+
+    SignatureVerifyResponse verifyResponse =
+        signatureVerify(signatureStub, publicKeyset, signature, data);
+    assertThat(verifyResponse.getErr()).isEmpty();
+  }
+
+  @Test
+  public void signatureSign_failsOnBadKeyset() throws Exception {
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    byte[] data = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+
+    SignatureSignResponse response = signatureSign(signatureStub, badKeyset, data);
+    assertThat(response.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void signatureVerify_failsOnBadSignature() throws Exception {
+    byte[] template = SignatureKeyTemplates.ECDSA_P256.toByteArray();
+    byte[] data = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+
+    KeysetGenerateResponse genResponse = generateKeyset(keysetStub, template);
+    assertThat(genResponse.getErr()).isEmpty();
+    byte[] privateKeyset = genResponse.getKeyset().toByteArray();
+
+    KeysetPublicResponse pubResponse = publicKeyset(keysetStub, privateKeyset);
+    assertThat(pubResponse.getErr()).isEmpty();
+    byte[] publicKeyset = pubResponse.getPublicKeyset().toByteArray();
+
+    SignatureVerifyResponse verifyResponse =
+        signatureVerify(signatureStub, publicKeyset, "bad signature".getBytes(UTF_8), data);
+    assertThat(verifyResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void signatureVerify_failsOnBadKeyset() throws Exception {
+    byte[] template = SignatureKeyTemplates.ECDSA_P256.toByteArray();
+    byte[] data = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+
+    KeysetGenerateResponse genResponse = generateKeyset(keysetStub, template);
+    assertThat(genResponse.getErr()).isEmpty();
+    byte[] privateKeyset = genResponse.getKeyset().toByteArray();
+
+    SignatureSignResponse signResponse = signatureSign(signatureStub, privateKeyset, data);
+    assertThat(signResponse.getErr()).isEmpty();
+    byte[] signature = signResponse.getSignature().toByteArray();
+
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    SignatureVerifyResponse verifyResponse =
+        signatureVerify(signatureStub, badKeyset, signature, data);
+    assertThat(verifyResponse.getErr()).isNotEmpty();
+  }
+
+}
diff --git a/testing/java_src/javatests/com/google/crypto/tink/testing/TestingServicesTest.java b/testing/java_src/javatests/com/google/crypto/tink/testing/TestingServicesTest.java
new file mode 100644
index 0000000..80289dc
--- /dev/null
+++ b/testing/java_src/javatests/com/google/crypto/tink/testing/TestingServicesTest.java
@@ -0,0 +1,555 @@
+// 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.testing;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import com.google.crypto.tink.BinaryKeysetReader;
+import com.google.crypto.tink.aead.AeadKeyTemplates;
+import com.google.crypto.tink.config.TinkConfig;
+import com.google.crypto.tink.daead.DeterministicAeadKeyTemplates;
+import com.google.crypto.tink.mac.MacKeyTemplates;
+import com.google.crypto.tink.proto.Keyset;
+import com.google.crypto.tink.proto.testing.AeadDecryptRequest;
+import com.google.crypto.tink.proto.testing.AeadDecryptResponse;
+import com.google.crypto.tink.proto.testing.AeadEncryptRequest;
+import com.google.crypto.tink.proto.testing.AeadEncryptResponse;
+import com.google.crypto.tink.proto.testing.AeadGrpc;
+import com.google.crypto.tink.proto.testing.ComputeMacRequest;
+import com.google.crypto.tink.proto.testing.ComputeMacResponse;
+import com.google.crypto.tink.proto.testing.DeterministicAeadDecryptRequest;
+import com.google.crypto.tink.proto.testing.DeterministicAeadDecryptResponse;
+import com.google.crypto.tink.proto.testing.DeterministicAeadEncryptRequest;
+import com.google.crypto.tink.proto.testing.DeterministicAeadEncryptResponse;
+import com.google.crypto.tink.proto.testing.DeterministicAeadGrpc;
+import com.google.crypto.tink.proto.testing.KeysetFromJsonRequest;
+import com.google.crypto.tink.proto.testing.KeysetFromJsonResponse;
+import com.google.crypto.tink.proto.testing.KeysetGenerateRequest;
+import com.google.crypto.tink.proto.testing.KeysetGenerateResponse;
+import com.google.crypto.tink.proto.testing.KeysetGrpc;
+import com.google.crypto.tink.proto.testing.KeysetToJsonRequest;
+import com.google.crypto.tink.proto.testing.KeysetToJsonResponse;
+import com.google.crypto.tink.proto.testing.MacGrpc;
+import com.google.crypto.tink.proto.testing.MetadataGrpc;
+import com.google.crypto.tink.proto.testing.ServerInfoRequest;
+import com.google.crypto.tink.proto.testing.ServerInfoResponse;
+import com.google.crypto.tink.proto.testing.StreamingAeadDecryptRequest;
+import com.google.crypto.tink.proto.testing.StreamingAeadDecryptResponse;
+import com.google.crypto.tink.proto.testing.StreamingAeadEncryptRequest;
+import com.google.crypto.tink.proto.testing.StreamingAeadEncryptResponse;
+import com.google.crypto.tink.proto.testing.StreamingAeadGrpc;
+import com.google.crypto.tink.proto.testing.VerifyMacRequest;
+import com.google.crypto.tink.proto.testing.VerifyMacResponse;
+import com.google.crypto.tink.streamingaead.StreamingAeadKeyTemplates;
+import com.google.protobuf.ByteString;
+import io.grpc.ManagedChannel;
+import io.grpc.Server;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.grpc.inprocess.InProcessServerBuilder;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class TestingServicesTest {
+  private Server server;
+  private ManagedChannel channel;
+  MetadataGrpc.MetadataBlockingStub metadataStub;
+  KeysetGrpc.KeysetBlockingStub keysetStub;
+  AeadGrpc.AeadBlockingStub aeadStub;
+  DeterministicAeadGrpc.DeterministicAeadBlockingStub daeadStub;
+  StreamingAeadGrpc.StreamingAeadBlockingStub streamingAeadStub;
+  MacGrpc.MacBlockingStub macStub;
+
+  @Before
+  public void setUp() throws Exception {
+    TinkConfig.register();
+    String serverName = InProcessServerBuilder.generateName();
+    server = InProcessServerBuilder
+        .forName(serverName)
+        .directExecutor()
+        .addService(new MetadataServiceImpl())
+        .addService(new KeysetServiceImpl())
+        .addService(new AeadServiceImpl())
+        .addService(new DeterministicAeadServiceImpl())
+        .addService(new StreamingAeadServiceImpl())
+        .addService(new MacServiceImpl())
+        .build()
+        .start();
+    channel = InProcessChannelBuilder
+        .forName(serverName)
+        .directExecutor()
+        .build();
+    metadataStub = MetadataGrpc.newBlockingStub(channel);
+    keysetStub = KeysetGrpc.newBlockingStub(channel);
+    aeadStub = AeadGrpc.newBlockingStub(channel);
+    daeadStub = DeterministicAeadGrpc.newBlockingStub(channel);
+    streamingAeadStub = StreamingAeadGrpc.newBlockingStub(channel);
+    macStub = MacGrpc.newBlockingStub(channel);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    assertThat(channel.shutdown().awaitTermination(5, SECONDS)).isTrue();
+    assertThat(server.shutdown().awaitTermination(5, SECONDS)).isTrue();
+  }
+
+  private static KeysetGenerateResponse generateKeyset(
+      KeysetGrpc.KeysetBlockingStub keysetStub, byte[] template) {
+    KeysetGenerateRequest genRequest =
+        KeysetGenerateRequest.newBuilder().setTemplate(ByteString.copyFrom(template)).build();
+    return keysetStub.generate(genRequest);
+  }
+
+  private static KeysetToJsonResponse keysetToJson(
+      KeysetGrpc.KeysetBlockingStub keysetStub, byte[] keyset) {
+    KeysetToJsonRequest request =
+        KeysetToJsonRequest.newBuilder().setKeyset(ByteString.copyFrom(keyset)).build();
+    return keysetStub.toJson(request);
+  }
+
+  private static KeysetFromJsonResponse keysetFromJson(
+      KeysetGrpc.KeysetBlockingStub keysetStub, String jsonKeyset) {
+    KeysetFromJsonRequest request =
+        KeysetFromJsonRequest.newBuilder().setJsonKeyset(jsonKeyset).build();
+    return keysetStub.fromJson(request);
+  }
+
+  @Test
+  public void toJson_success() throws Exception {
+    String jsonKeyset =
+        ""
+            + "{"
+            + "  \"primaryKeyId\": 42,"
+            + "  \"key\": ["
+            + "    {"
+            + "      \"keyData\": {"
+            + "        \"typeUrl\": \"type.googleapis.com/google.crypto.tink.AesGcmKey\","
+            + "        \"keyMaterialType\": \"SYMMETRIC\","
+            + "        \"value\": \"AFakeTestKeyValue1234567\""
+            + "      },"
+            + "      \"outputPrefixType\": \"TINK\","
+            + "      \"keyId\": 42,"
+            + "      \"status\": \"ENABLED\""
+            + "    }"
+            + "  ]"
+            + "})";
+    KeysetFromJsonResponse fromResponse = keysetFromJson(keysetStub, jsonKeyset);
+    assertThat(fromResponse.getErr()).isEmpty();
+    byte[] output = fromResponse.getKeyset().toByteArray();
+
+    Keyset keyset = BinaryKeysetReader.withBytes(output).read();
+    assertThat(keyset.getPrimaryKeyId()).isEqualTo(42);
+  }
+
+  @Test
+  public void toFromJson_success() throws Exception {
+    byte[] template = AeadKeyTemplates.AES128_GCM.toByteArray();
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    KeysetToJsonResponse toResponse = keysetToJson(keysetStub, keyset);
+    assertThat(toResponse.getErr()).isEmpty();
+    String jsonKeyset = toResponse.getJsonKeyset();
+
+    KeysetFromJsonResponse fromResponse = keysetFromJson(keysetStub, jsonKeyset);
+    assertThat(fromResponse.getErr()).isEmpty();
+    byte[] output = fromResponse.getKeyset().toByteArray();
+
+    assertThat(output).isEqualTo(keyset);
+  }
+
+  private static AeadEncryptResponse aeadEncrypt(
+      AeadGrpc.AeadBlockingStub aeadStub, byte[] keyset, byte[] plaintext, byte[] associatedData) {
+    AeadEncryptRequest encRequest =
+        AeadEncryptRequest.newBuilder()
+            .setKeyset(ByteString.copyFrom(keyset))
+            .setPlaintext(ByteString.copyFrom(plaintext))
+            .setAssociatedData(ByteString.copyFrom(associatedData))
+            .build();
+    return aeadStub.encrypt(encRequest);
+  }
+
+  private static AeadDecryptResponse aeadDecrypt(
+      AeadGrpc.AeadBlockingStub aeadStub, byte[] keyset, byte[] ciphertext, byte[] associatedData) {
+    AeadDecryptRequest decRequest =
+        AeadDecryptRequest.newBuilder()
+            .setKeyset(ByteString.copyFrom(keyset))
+            .setCiphertext(ByteString.copyFrom(ciphertext))
+            .setAssociatedData(ByteString.copyFrom(associatedData))
+            .build();
+    return aeadStub.decrypt(decRequest);
+  }
+
+  @Test
+  public void aeadGenerateEncryptDecrypt_success() throws Exception {
+    byte[] template = AeadKeyTemplates.AES128_GCM.toByteArray();
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "generate_encrypt_decrypt".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    AeadEncryptResponse encResponse = aeadEncrypt(aeadStub, keyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isEmpty();
+    byte[] ciphertext = encResponse.getCiphertext().toByteArray();
+
+    AeadDecryptResponse decResponse = aeadDecrypt(aeadStub, keyset, ciphertext, associatedData);
+    assertThat(decResponse.getErr()).isEmpty();
+    byte[] output = decResponse.getPlaintext().toByteArray();
+
+    assertThat(output).isEqualTo(plaintext);
+  }
+
+  @Test
+  public void generateKeyset_failsOnBadTemplate() throws Exception {
+    byte[] badTemplate = "bad template".getBytes(UTF_8);
+    KeysetGenerateResponse genResponse = generateKeyset(keysetStub, badTemplate);
+    assertThat(genResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void aeadEncrypt_failsOnBadKeyset() throws Exception {
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "aead_encrypt_fails_on_bad_keyset".getBytes(UTF_8);
+    AeadEncryptResponse encResponse = aeadEncrypt(aeadStub, badKeyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void aeadDecrypt_failsOnBadCiphertext() throws Exception {
+    byte[] template = AeadKeyTemplates.AES128_GCM.toByteArray();
+    byte[] badCiphertext = "bad ciphertext".getBytes(UTF_8);
+    byte[] associatedData = "aead_decrypt_fails_on_bad_ciphertext".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    AeadDecryptResponse decResponse = aeadDecrypt(aeadStub, keyset, badCiphertext, associatedData);
+    assertThat(decResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void aeadDecrypt_failsOnBadKeyset() throws Exception {
+    byte[] template = AeadKeyTemplates.AES128_GCM.toByteArray();
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "generate_encrypt_decrypt".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    AeadEncryptResponse encResponse = aeadEncrypt(aeadStub, keyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isEmpty();
+    byte[] ciphertext = encResponse.getCiphertext().toByteArray();
+
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+
+    AeadDecryptResponse decResponse = aeadDecrypt(aeadStub, badKeyset, ciphertext, associatedData);
+    assertThat(decResponse.getErr()).isNotEmpty();
+  }
+
+  private static DeterministicAeadEncryptResponse daeadEncrypt(
+      DeterministicAeadGrpc.DeterministicAeadBlockingStub daeadStub,
+      byte[] keyset,
+      byte[] plaintext,
+      byte[] associatedData) {
+    DeterministicAeadEncryptRequest encRequest =
+        DeterministicAeadEncryptRequest.newBuilder()
+            .setKeyset(ByteString.copyFrom(keyset))
+            .setPlaintext(ByteString.copyFrom(plaintext))
+            .setAssociatedData(ByteString.copyFrom(associatedData))
+            .build();
+    return daeadStub.encryptDeterministically(encRequest);
+  }
+
+  private static DeterministicAeadDecryptResponse daeadDecrypt(
+      DeterministicAeadGrpc.DeterministicAeadBlockingStub daeadStub,
+      byte[] keyset,
+      byte[] ciphertext,
+      byte[] associatedData) {
+    DeterministicAeadDecryptRequest decRequest =
+        DeterministicAeadDecryptRequest.newBuilder()
+            .setKeyset(ByteString.copyFrom(keyset))
+            .setCiphertext(ByteString.copyFrom(ciphertext))
+            .setAssociatedData(ByteString.copyFrom(associatedData))
+            .build();
+    return daeadStub.decryptDeterministically(decRequest);
+  }
+
+  @Test
+  public void daeadGenerateEncryptDecryptDeterministically_success() throws Exception {
+    byte[] template = DeterministicAeadKeyTemplates.AES256_SIV.toByteArray();
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "generate_encrypt_decrypt".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    DeterministicAeadEncryptResponse encResponse =
+        daeadEncrypt(daeadStub, keyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isEmpty();
+    byte[] ciphertext = encResponse.getCiphertext().toByteArray();
+
+    DeterministicAeadDecryptResponse decResponse =
+        daeadDecrypt(daeadStub, keyset, ciphertext, associatedData);
+    assertThat(decResponse.getErr()).isEmpty();
+    byte[] output = decResponse.getPlaintext().toByteArray();
+
+    assertThat(output).isEqualTo(plaintext);
+  }
+
+  @Test
+  public void daeadEncryptDeterministically_failsOnBadKeyset() throws Exception {
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "aead_encrypt_fails_on_bad_keyset".getBytes(UTF_8);
+    DeterministicAeadEncryptResponse encResponse =
+        daeadEncrypt(daeadStub, badKeyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void daeadDecryptDeterministically_failsOnBadCiphertext() throws Exception {
+    byte[] template = DeterministicAeadKeyTemplates.AES256_SIV.toByteArray();
+    byte[] badCiphertext = "bad ciphertext".getBytes(UTF_8);
+    byte[] associatedData = "aead_decrypt_fails_on_bad_ciphertext".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    DeterministicAeadDecryptResponse decResponse =
+        daeadDecrypt(daeadStub, keyset, badCiphertext, associatedData);
+    assertThat(decResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void daeadDecryptDeterministically_failsOnBadKeyset() throws Exception {
+    byte[] template = DeterministicAeadKeyTemplates.AES256_SIV.toByteArray();
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "generate_encrypt_decrypt".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    DeterministicAeadEncryptResponse encResponse =
+        daeadEncrypt(daeadStub, keyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isEmpty();
+    byte[] ciphertext = encResponse.getCiphertext().toByteArray();
+
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+
+    DeterministicAeadDecryptResponse decResponse =
+        daeadDecrypt(daeadStub, badKeyset, ciphertext, associatedData);
+    assertThat(decResponse.getErr()).isNotEmpty();
+  }
+
+  private static StreamingAeadEncryptResponse streamingAeadEncrypt(
+      StreamingAeadGrpc.StreamingAeadBlockingStub streamingAeadStub,
+      byte[] keyset,
+      byte[] plaintext,
+      byte[] associatedData) {
+    StreamingAeadEncryptRequest encRequest =
+        StreamingAeadEncryptRequest.newBuilder()
+            .setKeyset(ByteString.copyFrom(keyset))
+            .setPlaintext(ByteString.copyFrom(plaintext))
+            .setAssociatedData(ByteString.copyFrom(associatedData))
+            .build();
+    return streamingAeadStub.encrypt(encRequest);
+  }
+
+  private static StreamingAeadDecryptResponse streamingAeadDecrypt(
+      StreamingAeadGrpc.StreamingAeadBlockingStub streamingAeadStub,
+      byte[] keyset,
+      byte[] ciphertext,
+      byte[] associatedData) {
+    StreamingAeadDecryptRequest decRequest =
+        StreamingAeadDecryptRequest.newBuilder()
+            .setKeyset(ByteString.copyFrom(keyset))
+            .setCiphertext(ByteString.copyFrom(ciphertext))
+            .setAssociatedData(ByteString.copyFrom(associatedData))
+            .build();
+    return streamingAeadStub.decrypt(decRequest);
+  }
+
+  @Test
+  public void streamingAeadGenerateEncryptDecrypt_success() throws Exception {
+    byte[] template = StreamingAeadKeyTemplates.AES128_CTR_HMAC_SHA256_4KB.toByteArray();
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "generate_encrypt_decrypt".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    StreamingAeadEncryptResponse encResponse = streamingAeadEncrypt(
+        streamingAeadStub, keyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isEmpty();
+    byte[] ciphertext = encResponse.getCiphertext().toByteArray();
+
+    StreamingAeadDecryptResponse decResponse = streamingAeadDecrypt(
+        streamingAeadStub, keyset, ciphertext, associatedData);
+    assertThat(decResponse.getErr()).isEmpty();
+    byte[] output = decResponse.getPlaintext().toByteArray();
+
+    assertThat(output).isEqualTo(plaintext);
+  }
+
+  @Test
+  public void streamingAeadEncrypt_failsOnBadKeyset() throws Exception {
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "streamingAead_encrypt_fails_on_bad_keyset".getBytes(UTF_8);
+    StreamingAeadEncryptResponse encResponse = streamingAeadEncrypt(
+        streamingAeadStub, badKeyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void streamingAeadDecrypt_failsOnBadCiphertext() throws Exception {
+    byte[] template = StreamingAeadKeyTemplates.AES128_CTR_HMAC_SHA256_4KB.toByteArray();
+    byte[] badCiphertext = "bad ciphertext".getBytes(UTF_8);
+    byte[] associatedData = "streamingAead_decrypt_fails_on_bad_ciphertext".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    StreamingAeadDecryptResponse decResponse = streamingAeadDecrypt(
+        streamingAeadStub, keyset, badCiphertext, associatedData);
+    assertThat(decResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void streamingAeadDecrypt_failsOnBadKeyset() throws Exception {
+    byte[] template = StreamingAeadKeyTemplates.AES128_CTR_HMAC_SHA256_4KB.toByteArray();
+    byte[] plaintext = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+    byte[] associatedData = "generate_encrypt_decrypt".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    StreamingAeadEncryptResponse encResponse = streamingAeadEncrypt(
+        streamingAeadStub, keyset, plaintext, associatedData);
+    assertThat(encResponse.getErr()).isEmpty();
+    byte[] ciphertext = encResponse.getCiphertext().toByteArray();
+
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+
+    StreamingAeadDecryptResponse decResponse = streamingAeadDecrypt(
+        streamingAeadStub, badKeyset, ciphertext, associatedData);
+    assertThat(decResponse.getErr()).isNotEmpty();
+  }
+
+  private static ComputeMacResponse computeMac(
+      MacGrpc.MacBlockingStub macStub, byte[] keyset, byte[] data) {
+    ComputeMacRequest request =
+        ComputeMacRequest.newBuilder()
+            .setKeyset(ByteString.copyFrom(keyset))
+            .setData(ByteString.copyFrom(data))
+            .build();
+    return macStub.computeMac(request);
+  }
+
+  private static VerifyMacResponse verifyMac(
+      MacGrpc.MacBlockingStub macStub, byte[] keyset, byte[] macValue, byte[] data) {
+    VerifyMacRequest request =
+        VerifyMacRequest.newBuilder()
+            .setKeyset(ByteString.copyFrom(keyset))
+            .setMacValue(ByteString.copyFrom(macValue))
+            .setData(ByteString.copyFrom(data))
+            .build();
+    return macStub.verifyMac(request);
+  }
+
+  @Test
+  public void computeVerifyMac_success() throws Exception {
+    byte[] template = MacKeyTemplates.HMAC_SHA256_128BITTAG.toByteArray();
+    byte[] data = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    ComputeMacResponse compResponse = computeMac(macStub, keyset, data);
+    assertThat(compResponse.getErr()).isEmpty();
+    byte[] macValue = compResponse.getMacValue().toByteArray();
+
+    VerifyMacResponse verifyResponse = verifyMac(macStub, keyset, macValue, data);
+    assertThat(verifyResponse.getErr()).isEmpty();
+  }
+
+  @Test
+  public void computeMac_failsOnBadKeyset() throws Exception {
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    byte[] data = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+
+    ComputeMacResponse compResponse = computeMac(macStub, badKeyset, data);
+    assertThat(compResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void verifyMac_failsOnBadMacValue() throws Exception {
+    byte[] template = MacKeyTemplates.HMAC_SHA256_128BITTAG.toByteArray();
+    byte[] data = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    VerifyMacResponse verifyResponse =
+        verifyMac(macStub, keyset, "bad mac_value".getBytes(UTF_8), data);
+    assertThat(verifyResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void verifyMac_failsOnBadKeyset() throws Exception {
+    byte[] template = MacKeyTemplates.HMAC_SHA256_128BITTAG.toByteArray();
+    byte[] data = "The quick brown fox jumps over the lazy dog".getBytes(UTF_8);
+
+    KeysetGenerateResponse keysetResponse = generateKeyset(keysetStub, template);
+    assertThat(keysetResponse.getErr()).isEmpty();
+    byte[] keyset = keysetResponse.getKeyset().toByteArray();
+
+    ComputeMacResponse compResponse = computeMac(macStub, keyset, data);
+    assertThat(compResponse.getErr()).isEmpty();
+    byte[] macValue = compResponse.getMacValue().toByteArray();
+
+    byte[] badKeyset = "bad keyset".getBytes(UTF_8);
+    VerifyMacResponse verifyResponse = verifyMac(macStub, badKeyset, macValue, data);
+    assertThat(verifyResponse.getErr()).isNotEmpty();
+  }
+
+  @Test
+  public void getServerInfo_success() throws Exception {
+    ServerInfoResponse response =
+        metadataStub.getServerInfo(ServerInfoRequest.getDefaultInstance());
+    assertThat(response.getLanguage()).isEqualTo("java");
+    assertThat(response.getTinkVersion()).isNotEmpty();
+  }
+}
diff --git a/testing/python/.bazelversion b/testing/python/.bazelversion
new file mode 100644
index 0000000..fd2a018
--- /dev/null
+++ b/testing/python/.bazelversion
@@ -0,0 +1 @@
+3.1.0
diff --git a/testing/python/BUILD.bazel b/testing/python/BUILD.bazel
new file mode 100644
index 0000000..ecabedf
--- /dev/null
+++ b/testing/python/BUILD.bazel
@@ -0,0 +1,69 @@
+load("@rules_proto_grpc//python:defs.bzl", "python_grpc_library")
+load("@rules_python//python:defs.bzl", "py_binary", "py_library")
+load("@tink_py_pip_deps//:requirements.bzl", "requirement")
+
+package(
+    default_testonly = 1,
+    default_visibility = ["//visibility:public"],
+)
+
+licenses(["notice"])
+
+python_grpc_library(
+    name = "testing_api_python_library",
+    deps = ["@tink_base//proto/testing:testing_api_proto"],
+)
+
+py_library(
+    name = "services",
+    srcs = ["services.py"],
+    srcs_version = "PY3",
+    deps = [
+        ":testing_api_python_library",
+        "@com_google_protobuf//:protobuf_python",
+        "@tink_py//tink:cleartext_keyset_handle",
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/aead",
+        "@tink_py//tink/daead",
+        "@tink_py//tink/hybrid",
+        "@tink_py//tink/mac",
+        "@tink_py//tink/signature",
+    ],
+)
+
+py_test(
+    name = "services_test",
+    srcs = ["services_test.py"],
+    python_version = "PY3",
+    srcs_version = "PY3",
+    deps = [
+        ":services",
+        ":testing_api_python_library",
+        requirement("absl-py"),
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/aead",
+        "@tink_py//tink/daead",
+        "@tink_py//tink/hybrid",
+        "@tink_py//tink/mac",
+        "@tink_py//tink/signature",
+    ],
+)
+
+py_binary(
+    name = "testing_server",
+    srcs = ["testing_server.py"],
+    python_version = "PY3",
+    srcs_version = "PY3",
+    deps = [
+        ":services",
+        ":testing_api_python_library",
+        "@com_google_protobuf//:protobuf_python",
+        "@tink_py//tink:cleartext_keyset_handle",
+        "@tink_py//tink:tink_python",
+        "@tink_py//tink/aead",
+        "@tink_py//tink/daead",
+        "@tink_py//tink/hybrid",
+        "@tink_py//tink/mac",
+        "@tink_py//tink/signature",
+    ],
+)
diff --git a/testing/python/WORKSPACE b/testing/python/WORKSPACE
new file mode 100644
index 0000000..31d3ee8
--- /dev/null
+++ b/testing/python/WORKSPACE
@@ -0,0 +1,89 @@
+workspace(name = "testing_python")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+local_repository(
+    name = "tink_base",
+    path = "../..",
+)
+
+local_repository(
+    name = "tink_py",
+    path = "../../python",
+)
+
+local_repository(
+    name = "tink_cc",
+    path = "../../cc",
+)
+
+# NOTE: The Python dependencies have to be loaded first, as they rely on a
+# newer version of rules_python.
+load("@tink_py//:tink_py_deps.bzl", "tink_py_deps")
+tink_py_deps()
+
+load("@tink_py//:tink_py_deps_init.bzl", "tink_py_deps_init")
+tink_py_deps_init("tink_py")
+
+load("@tink_py_pip_deps//:requirements.bzl", tink_pip_install = "pip_install")
+tink_pip_install()
+
+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()
+
+load("@tink_cc//:tink_cc_deps.bzl", "tink_cc_deps")
+tink_cc_deps()
+
+load("@tink_cc//:tink_cc_deps_init.bzl", "tink_cc_deps_init")
+tink_cc_deps_init()
+
+http_archive(
+    name = "rules_python",
+    strip_prefix = "rules_python-94677401bc56ed5d756f50b441a6a5c7f735a6d4",
+    url = "https://github.com/bazelbuild/rules_python/archive/94677401bc56ed5d756f50b441a6a5c7f735a6d4.zip",
+    sha256 = "de39bc4d6605e6d395faf5e07516c64c8d833404ee3eb132b5ff1161f9617dec",
+)
+
+http_archive(
+    name = "rules_proto_grpc",
+    urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/1.0.2.tar.gz"],
+    sha256 = "5f0f2fc0199810c65a2de148a52ba0aff14d631d4e8202f41aff6a9d590a471b",
+    strip_prefix = "rules_proto_grpc-1.0.2",
+)
+
+load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_toolchains", "rules_proto_grpc_repos")
+rules_proto_grpc_toolchains()
+rules_proto_grpc_repos()
+
+load("@rules_proto_grpc//python:repositories.bzl", rules_proto_grpc_python_repos="python_repos")
+rules_proto_grpc_python_repos()
+
+load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps")
+
+grpc_deps()
+
+load("@rules_python//python:repositories.bzl", "py_repositories")
+py_repositories()
+
+load("@rules_python//python:pip.bzl", "pip_repositories", "pip3_import")
+pip_repositories()
+
+pip3_import(
+   name = "pip_deps",
+   requirements = "//:requirements.txt",
+)
+
+load("@pip_deps//:requirements.bzl", "pip_install")
+pip_install()
+
+pip3_import(
+    name = "rules_proto_grpc_py3_deps",
+    requirements = "@rules_proto_grpc//python:requirements.txt",
+)
+
+load("@rules_proto_grpc_py3_deps//:requirements.bzl", pip3_install="pip_install")
+pip3_install()
+
diff --git a/testing/python/external/portpicker.BUILD.bazel b/testing/python/external/portpicker.BUILD.bazel
new file mode 100644
index 0000000..57e2ec3
--- /dev/null
+++ b/testing/python/external/portpicker.BUILD.bazel
@@ -0,0 +1,13 @@
+# Description:
+#   Import of portpicker library.
+
+licenses(["notice"])
+
+exports_files(["LICENSE"])
+
+py_library(
+    name = "portpicker",
+    srcs = glob(["*.py"]),
+    srcs_version = "PY2AND3",
+    visibility = ["//visibility:public"],
+)
diff --git a/testing/python/requirements.txt b/testing/python/requirements.txt
new file mode 100644
index 0000000..b998a06
--- /dev/null
+++ b/testing/python/requirements.txt
@@ -0,0 +1 @@
+absl-py
diff --git a/testing/python/services.py b/testing/python/services.py
new file mode 100644
index 0000000..9a06d82
--- /dev/null
+++ b/testing/python/services.py
@@ -0,0 +1,261 @@
+# 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.
+"""Testing service API implementations in Python."""
+
+from __future__ import absolute_import
+from __future__ import division
+# Placeholder for import for type annotations
+from __future__ import print_function
+
+import io
+
+import grpc
+import tink
+from tink import aead
+from tink import cleartext_keyset_handle
+from tink import daead
+from tink import hybrid
+from tink import mac
+from tink import signature
+from tink.proto import tink_pb2
+from proto.testing import testing_api_pb2
+from proto.testing import testing_api_pb2_grpc
+
+
+class MetadataServicer(testing_api_pb2_grpc.MetadataServicer):
+  """A service with metadata about the server."""
+
+  def GetServerInfo(
+      self, request: testing_api_pb2.ServerInfoRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.ServerInfoResponse:
+    """Generates a keyset."""
+    return testing_api_pb2.ServerInfoResponse(language='python')
+
+
+class KeysetServicer(testing_api_pb2_grpc.KeysetServicer):
+  """A service for testing Keyset operations."""
+
+  def Generate(
+      self, request: testing_api_pb2.KeysetGenerateRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.KeysetGenerateResponse:
+    """Generates a keyset."""
+    try:
+      template = tink_pb2.KeyTemplate()
+      template.ParseFromString(request.template)
+      keyset_handle = tink.new_keyset_handle(template)
+      keyset = io.BytesIO()
+      cleartext_keyset_handle.write(
+          tink.BinaryKeysetWriter(keyset), keyset_handle)
+      return testing_api_pb2.KeysetGenerateResponse(keyset=keyset.getvalue())
+    except tink.TinkError as e:
+      return testing_api_pb2.KeysetGenerateResponse(err=str(e))
+
+  def Public(
+      self, request: testing_api_pb2.KeysetPublicRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.KeysetPublicResponse:
+    """Generates a public-key keyset from a private-key keyset."""
+    try:
+      private_keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.private_keyset))
+      public_keyset_handle = private_keyset_handle.public_keyset_handle()
+      public_keyset = io.BytesIO()
+      cleartext_keyset_handle.write(
+          tink.BinaryKeysetWriter(public_keyset), public_keyset_handle)
+      return testing_api_pb2.KeysetPublicResponse(
+          public_keyset=public_keyset.getvalue())
+    except tink.TinkError as e:
+      return testing_api_pb2.KeysetPublicResponse(err=str(e))
+
+  def ToJson(
+      self, request: testing_api_pb2.KeysetToJsonRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.KeysetToJsonResponse:
+    """Converts a keyset from binary to JSON format."""
+    try:
+      keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.keyset))
+      json_keyset = io.StringIO()
+      cleartext_keyset_handle.write(
+          tink.JsonKeysetWriter(json_keyset), keyset_handle)
+      return testing_api_pb2.KeysetToJsonResponse(
+          json_keyset=json_keyset.getvalue())
+    except tink.TinkError as e:
+      return testing_api_pb2.KeysetToJsonResponse(err=str(e))
+
+  def FromJson(
+      self, request: testing_api_pb2.KeysetFromJsonRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.KeysetFromJsonResponse:
+    """Converts a keyset from JSON to binary format."""
+    try:
+      keyset_handle = cleartext_keyset_handle.read(
+          tink.JsonKeysetReader(request.json_keyset))
+      keyset = io.BytesIO()
+      cleartext_keyset_handle.write(
+          tink.BinaryKeysetWriter(keyset), keyset_handle)
+      return testing_api_pb2.KeysetFromJsonResponse(keyset=keyset.getvalue())
+    except tink.TinkError as e:
+      return testing_api_pb2.KeysetFromJsonResponse(err=str(e))
+
+
+class AeadServicer(testing_api_pb2_grpc.AeadServicer):
+  """A service for testing AEAD encryption."""
+
+  def Encrypt(
+      self, request: testing_api_pb2.AeadEncryptRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.AeadEncryptResponse:
+    """Encrypts a message."""
+    try:
+      keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.keyset))
+      p = keyset_handle.primitive(aead.Aead)
+      ciphertext = p.encrypt(request.plaintext, request.associated_data)
+      return testing_api_pb2.AeadEncryptResponse(ciphertext=ciphertext)
+    except tink.TinkError as e:
+      return testing_api_pb2.AeadEncryptResponse(err=str(e))
+
+  def Decrypt(
+      self, request: testing_api_pb2.AeadDecryptRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.AeadDecryptResponse:
+    """Decrypts a message."""
+    try:
+      keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.keyset))
+      p = keyset_handle.primitive(aead.Aead)
+      plaintext = p.decrypt(request.ciphertext, request.associated_data)
+      return testing_api_pb2.AeadDecryptResponse(plaintext=plaintext)
+    except tink.TinkError as e:
+      return testing_api_pb2.AeadDecryptResponse(err=str(e))
+
+
+class DeterministicAeadServicer(testing_api_pb2_grpc.DeterministicAeadServicer):
+  """A service for testing Deterministic AEAD encryption."""
+
+  def EncryptDeterministically(
+      self, request: testing_api_pb2.DeterministicAeadEncryptRequest,
+      context: grpc.ServicerContext
+  ) -> testing_api_pb2.DeterministicAeadEncryptResponse:
+    """Encrypts a message."""
+    try:
+      keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.keyset))
+      p = keyset_handle.primitive(daead.DeterministicAead)
+      ciphertext = p.encrypt_deterministically(request.plaintext,
+                                               request.associated_data)
+      return testing_api_pb2.DeterministicAeadEncryptResponse(
+          ciphertext=ciphertext)
+    except tink.TinkError as e:
+      return testing_api_pb2.DeterministicAeadEncryptResponse(err=str(e))
+
+  def DecryptDeterministically(
+      self, request: testing_api_pb2.DeterministicAeadDecryptRequest,
+      context: grpc.ServicerContext
+  ) -> testing_api_pb2.DeterministicAeadDecryptResponse:
+    """Decrypts a message."""
+    try:
+      keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.keyset))
+      p = keyset_handle.primitive(daead.DeterministicAead)
+      plaintext = p.decrypt_deterministically(request.ciphertext,
+                                              request.associated_data)
+      return testing_api_pb2.DeterministicAeadDecryptResponse(
+          plaintext=plaintext)
+    except tink.TinkError as e:
+      return testing_api_pb2.DeterministicAeadDecryptResponse(err=str(e))
+
+
+class MacServicer(testing_api_pb2_grpc.MacServicer):
+  """A service for testing MACs."""
+
+  def ComputeMac(
+      self, request: testing_api_pb2.ComputeMacRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.ComputeMacResponse:
+    """Computes a MAC."""
+    try:
+      keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.keyset))
+      p = keyset_handle.primitive(mac.Mac)
+      mac_value = p.compute_mac(request.data)
+      return testing_api_pb2.ComputeMacResponse(mac_value=mac_value)
+    except tink.TinkError as e:
+      return testing_api_pb2.ComputeMacResponse(err=str(e))
+
+  def VerifyMac(
+      self, request: testing_api_pb2.VerifyMacRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.VerifyMacResponse:
+    """Verifies a MAC value."""
+    try:
+      keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.keyset))
+      p = keyset_handle.primitive(mac.Mac)
+      p.verify_mac(request.mac_value, request.data)
+      return testing_api_pb2.VerifyMacResponse()
+    except tink.TinkError as e:
+      return testing_api_pb2.VerifyMacResponse(err=str(e))
+
+
+class HybridServicer(testing_api_pb2_grpc.HybridServicer):
+  """A service for testing hybrid encryption and decryption."""
+
+  def Encrypt(
+      self, request: testing_api_pb2.HybridEncryptRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.HybridEncryptResponse:
+    """Encrypts a message."""
+    try:
+      public_keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.public_keyset))
+      p = public_keyset_handle.primitive(hybrid.HybridEncrypt)
+      ciphertext = p.encrypt(request.plaintext, request.context_info)
+      return testing_api_pb2.HybridEncryptResponse(ciphertext=ciphertext)
+    except tink.TinkError as e:
+      return testing_api_pb2.HybridEncryptResponse(err=str(e))
+
+  def Decrypt(
+      self, request: testing_api_pb2.HybridDecryptRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.HybridDecryptResponse:
+    """Decrypts a message."""
+    try:
+      private_keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.private_keyset))
+      p = private_keyset_handle.primitive(hybrid.HybridDecrypt)
+      plaintext = p.decrypt(request.ciphertext, request.context_info)
+      return testing_api_pb2.HybridDecryptResponse(plaintext=plaintext)
+    except tink.TinkError as e:
+      return testing_api_pb2.HybridDecryptResponse(err=str(e))
+
+
+class SignatureServicer(testing_api_pb2_grpc.SignatureServicer):
+  """A service for testing signatures."""
+
+  def Sign(
+      self, request: testing_api_pb2.SignatureSignRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.SignatureSignResponse:
+    """Signs a message."""
+    try:
+      private_keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.private_keyset))
+      p = private_keyset_handle.primitive(signature.PublicKeySign)
+      signature_value = p.sign(request.data)
+      return testing_api_pb2.SignatureSignResponse(signature=signature_value)
+    except tink.TinkError as e:
+      return testing_api_pb2.SignatureSignResponse(err=str(e))
+
+  def Verify(
+      self, request: testing_api_pb2.SignatureVerifyRequest,
+      context: grpc.ServicerContext) -> testing_api_pb2.SignatureVerifyResponse:
+    """Verifies a signature."""
+    try:
+      public_keyset_handle = cleartext_keyset_handle.read(
+          tink.BinaryKeysetReader(request.public_keyset))
+      p = public_keyset_handle.primitive(signature.PublicKeyVerify)
+      p.verify(request.signature, request.data)
+      return testing_api_pb2.SignatureVerifyResponse()
+    except tink.TinkError as e:
+      return testing_api_pb2.SignatureVerifyResponse(err=str(e))
diff --git a/testing/python/services_test.py b/testing/python/services_test.py
new file mode 100644
index 0000000..7ce6995
--- /dev/null
+++ b/testing/python/services_test.py
@@ -0,0 +1,402 @@
+# 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 tink.tools.testing.python.testing_server."""
+
+from absl import logging
+from absl.testing import absltest
+import grpc
+
+import tink
+from tink import aead
+from tink import daead
+from tink import hybrid
+from tink import mac
+from tink import signature
+
+
+from proto.testing import testing_api_pb2
+import services
+
+
+class DummyServicerContext(grpc.ServicerContext):
+
+  def is_active(self):
+    pass
+
+  def time_remaining(self):
+    pass
+
+  def cancel(self):
+    pass
+
+  def add_callback(self, callback):
+    pass
+
+  def invocation_metadata(self):
+    pass
+
+  def peer(self):
+    pass
+
+  def peer_identities(self):
+    pass
+
+  def peer_identity_key(self):
+    pass
+
+  def auth_context(self):
+    pass
+
+  def set_compression(self, compression):
+    pass
+
+  def send_initial_metadata(self, initial_metadata):
+    pass
+
+  def set_trailing_metadata(self, trailing_metadata):
+    pass
+
+  def abort(self, code, details):
+    pass
+
+  def abort_with_status(self, status):
+    pass
+
+  def set_code(self, code):
+    pass
+
+  def set_details(self, details):
+    pass
+
+  def disable_next_message_compression(self):
+    pass
+
+
+class ServicesTest(absltest.TestCase):
+
+  _ctx = DummyServicerContext()
+
+  @classmethod
+  def setUpClass(cls):
+    super().setUpClass()
+    aead.register()
+    daead.register()
+    mac.register()
+    hybrid.register()
+    signature.register()
+
+  def test_from_json(self):
+    keyset_servicer = services.KeysetServicer()
+    json_keyset = """
+        {
+          "primaryKeyId": 42,
+          "key": [
+            {
+              "keyData": {
+                "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
+                "keyMaterialType": "SYMMETRIC",
+                "value": "AFakeTestKeyValue1234567"
+
+              },
+              "outputPrefixType": "TINK",
+              "keyId": 42,
+              "status": "ENABLED"
+            }
+          ]
+        }"""
+    request = testing_api_pb2.KeysetFromJsonRequest(json_keyset=json_keyset)
+    response = keyset_servicer.FromJson(request, self._ctx)
+    self.assertEqual(response.WhichOneof('result'), 'keyset')
+    keyset = tink.BinaryKeysetReader(response.keyset).read()
+    self.assertEqual(keyset.primary_key_id, 42)
+    self.assertLen(keyset.key, 1)
+
+  def test_from_json_fail(self):
+    keyset_servicer = services.KeysetServicer()
+    request = testing_api_pb2.KeysetFromJsonRequest(json_keyset='bad json')
+    response = keyset_servicer.FromJson(request, self._ctx)
+    self.assertEqual(response.WhichOneof('result'), 'err')
+    self.assertNotEmpty(response.err)
+
+  def test_generate_to_from_json(self):
+    keyset_servicer = services.KeysetServicer()
+
+    template = aead.aead_key_templates.AES128_GCM.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    keyset = gen_response.keyset
+
+    tojson_request = testing_api_pb2.KeysetToJsonRequest(keyset=keyset)
+    tojson_response = keyset_servicer.ToJson(tojson_request, self._ctx)
+    self.assertEqual(tojson_response.WhichOneof('result'), 'json_keyset')
+    json_keyset = tojson_response.json_keyset
+
+    fromjson_request = testing_api_pb2.KeysetFromJsonRequest(
+        json_keyset=json_keyset)
+    fromjson_response = keyset_servicer.FromJson(fromjson_request, self._ctx)
+    self.assertEqual(fromjson_response.WhichOneof('result'), 'keyset')
+    self.assertEqual(fromjson_response.keyset, keyset)
+
+  def test_to_json_fail(self):
+    keyset_servicer = services.KeysetServicer()
+    request = testing_api_pb2.KeysetToJsonRequest(keyset=b'bad keyset')
+    response = keyset_servicer.ToJson(request, self._ctx)
+    self.assertEqual(response.WhichOneof('result'), 'err')
+    self.assertNotEmpty(response.err)
+
+  def test_generate_encrypt_decrypt(self):
+    keyset_servicer = services.KeysetServicer()
+    aead_servicer = services.AeadServicer()
+
+    template = aead.aead_key_templates.AES128_GCM.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    keyset = gen_response.keyset
+    plaintext = b'The quick brown fox jumps over the lazy dog'
+    associated_data = b'associated_data'
+    enc_request = testing_api_pb2.AeadEncryptRequest(
+        keyset=keyset, plaintext=plaintext, associated_data=associated_data)
+    enc_response = aead_servicer.Encrypt(enc_request, self._ctx)
+    self.assertEqual(enc_response.WhichOneof('result'), 'ciphertext')
+    ciphertext = enc_response.ciphertext
+    dec_request = testing_api_pb2.AeadDecryptRequest(
+        keyset=keyset, ciphertext=ciphertext, associated_data=associated_data)
+    dec_response = aead_servicer.Decrypt(dec_request, self._ctx)
+    self.assertEqual(dec_response.WhichOneof('result'), 'plaintext')
+    self.assertEqual(dec_response.plaintext, plaintext)
+
+  def test_generate_decrypt_fail(self):
+    keyset_servicer = services.KeysetServicer()
+    aead_servicer = services.AeadServicer()
+
+    template = aead.aead_key_templates.AES128_GCM.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    keyset = gen_response.keyset
+
+    ciphertext = b'some invalid ciphertext'
+    associated_data = b'associated_data'
+    dec_request = testing_api_pb2.AeadDecryptRequest(
+        keyset=keyset, ciphertext=ciphertext, associated_data=associated_data)
+    dec_response = aead_servicer.Decrypt(dec_request, self._ctx)
+    self.assertEqual(dec_response.WhichOneof('result'), 'err')
+    logging.info('Error in response: %s', dec_response.err)
+    self.assertNotEmpty(dec_response.err)
+
+  def test_server_info(self):
+    metadata_servicer = services.MetadataServicer()
+    request = testing_api_pb2.ServerInfoRequest()
+    response = metadata_servicer.GetServerInfo(request, self._ctx)
+    self.assertEqual(response.language, 'python')
+
+  def test_generate_encrypt_decrypt_deterministically(self):
+    keyset_servicer = services.KeysetServicer()
+    daead_servicer = services.DeterministicAeadServicer()
+
+    template_proto = daead.deterministic_aead_key_templates.AES256_SIV
+    template = template_proto.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    keyset = gen_response.keyset
+    plaintext = b'The quick brown fox jumps over the lazy dog'
+    associated_data = b'associated_data'
+    enc_request = testing_api_pb2.DeterministicAeadEncryptRequest(
+        keyset=keyset, plaintext=plaintext, associated_data=associated_data)
+    enc_response = daead_servicer.EncryptDeterministically(enc_request,
+                                                           self._ctx)
+    self.assertEqual(enc_response.WhichOneof('result'), 'ciphertext')
+    enc_response2 = daead_servicer.EncryptDeterministically(enc_request,
+                                                            self._ctx)
+    self.assertEqual(enc_response2.WhichOneof('result'), 'ciphertext')
+    self.assertEqual(enc_response2.ciphertext, enc_response.ciphertext)
+    ciphertext = enc_response.ciphertext
+    dec_request = testing_api_pb2.DeterministicAeadDecryptRequest(
+        keyset=keyset, ciphertext=ciphertext, associated_data=associated_data)
+    dec_response = daead_servicer.DecryptDeterministically(dec_request,
+                                                           self._ctx)
+    self.assertEqual(dec_response.WhichOneof('result'), 'plaintext')
+    self.assertEqual(dec_response.plaintext, plaintext)
+
+  def test_generate_decrypt_deterministically_fail(self):
+    keyset_servicer = services.KeysetServicer()
+    daead_servicer = services.DeterministicAeadServicer()
+
+    template_proto = daead.deterministic_aead_key_templates.AES256_SIV
+    template = template_proto.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    keyset = gen_response.keyset
+
+    ciphertext = b'some invalid ciphertext'
+    associated_data = b'associated_data'
+    dec_request = testing_api_pb2.DeterministicAeadDecryptRequest(
+        keyset=keyset, ciphertext=ciphertext, associated_data=associated_data)
+    dec_response = daead_servicer.DecryptDeterministically(dec_request,
+                                                           self._ctx)
+    self.assertEqual(dec_response.WhichOneof('result'), 'err')
+    logging.info('Error in response: %s', dec_response.err)
+    self.assertNotEmpty(dec_response.err)
+
+  def test_generate_compute_verify_mac(self):
+    keyset_servicer = services.KeysetServicer()
+    mac_servicer = services.MacServicer()
+
+    template = mac.mac_key_templates.HMAC_SHA256_128BITTAG.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    keyset = gen_response.keyset
+    data = b'The quick brown fox jumps over the lazy dog'
+    comp_request = testing_api_pb2.ComputeMacRequest(keyset=keyset, data=data)
+    comp_response = mac_servicer.ComputeMac(comp_request, self._ctx)
+    self.assertEqual(comp_response.WhichOneof('result'), 'mac_value')
+    mac_value = comp_response.mac_value
+    verify_request = testing_api_pb2.VerifyMacRequest(
+        keyset=keyset, mac_value=mac_value, data=data)
+    verify_response = mac_servicer.VerifyMac(verify_request, self._ctx)
+    self.assertEmpty(verify_response.err)
+
+  def test_generate_compute_verify_mac_fail(self):
+    keyset_servicer = services.KeysetServicer()
+    mac_servicer = services.MacServicer()
+
+    template = mac.mac_key_templates.HMAC_SHA256_128BITTAG.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    keyset = gen_response.keyset
+
+    verify_request = testing_api_pb2.VerifyMacRequest(
+        keyset=keyset, mac_value=b'invalid mac_value', data=b'data')
+    verify_response = mac_servicer.VerifyMac(verify_request, self._ctx)
+    logging.info('Error in response: %s', verify_response.err)
+    self.assertNotEmpty(verify_response.err)
+
+  def test_generate_hybrid_encrypt_decrypt(self):
+    keyset_servicer = services.KeysetServicer()
+    hybrid_servicer = services.HybridServicer()
+
+    tp = hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM
+    template = tp.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEmpty(gen_response.err)
+    private_keyset = gen_response.keyset
+
+    pub_request = testing_api_pb2.KeysetPublicRequest(
+        private_keyset=private_keyset)
+    pub_response = keyset_servicer.Public(pub_request, self._ctx)
+    self.assertEqual(pub_response.WhichOneof('result'), 'public_keyset')
+    public_keyset = pub_response.public_keyset
+
+    plaintext = b'The quick brown fox jumps over the lazy dog'
+    context_info = b'context_info'
+    enc_request = testing_api_pb2.HybridEncryptRequest(
+        public_keyset=public_keyset,
+        plaintext=plaintext,
+        context_info=context_info)
+    enc_response = hybrid_servicer.Encrypt(enc_request, self._ctx)
+    self.assertEqual(enc_response.WhichOneof('result'), 'ciphertext')
+    ciphertext = enc_response.ciphertext
+
+    dec_request = testing_api_pb2.HybridDecryptRequest(
+        private_keyset=private_keyset,
+        ciphertext=ciphertext,
+        context_info=context_info)
+    dec_response = hybrid_servicer.Decrypt(dec_request, self._ctx)
+    self.assertEqual(dec_response.WhichOneof('result'), 'plaintext')
+    self.assertEqual(dec_response.plaintext, plaintext)
+
+  def test_generate_hybrid_encrypt_decrypt_fail(self):
+    keyset_servicer = services.KeysetServicer()
+    hybrid_servicer = services.HybridServicer()
+
+    tp = hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM
+    template = tp.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    private_keyset = gen_response.keyset
+
+    dec_request = testing_api_pb2.HybridDecryptRequest(
+        private_keyset=private_keyset,
+        ciphertext=b'invalid ciphertext',
+        context_info=b'context_info')
+    dec_response = hybrid_servicer.Decrypt(dec_request, self._ctx)
+    self.assertEqual(dec_response.WhichOneof('result'), 'err')
+    self.assertNotEmpty(dec_response.err)
+
+  def test_sign_verify(self):
+    keyset_servicer = services.KeysetServicer()
+    signature_servicer = services.SignatureServicer()
+
+    template = signature.signature_key_templates.ECDSA_P256.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    private_keyset = gen_response.keyset
+
+    pub_request = testing_api_pb2.KeysetPublicRequest(
+        private_keyset=private_keyset)
+    pub_response = keyset_servicer.Public(pub_request, self._ctx)
+    self.assertEqual(pub_response.WhichOneof('result'), 'public_keyset')
+    public_keyset = pub_response.public_keyset
+
+    data = b'The quick brown fox jumps over the lazy dog'
+
+    sign_request = testing_api_pb2.SignatureSignRequest(
+        private_keyset=private_keyset,
+        data=data)
+    sign_response = signature_servicer.Sign(sign_request, self._ctx)
+    self.assertEqual(sign_response.WhichOneof('result'), 'signature')
+    a_signature = sign_response.signature
+
+    verify_request = testing_api_pb2.SignatureVerifyRequest(
+        public_keyset=public_keyset,
+        signature=a_signature,
+        data=data)
+    verify_response = signature_servicer.Verify(verify_request, self._ctx)
+    self.assertEmpty(verify_response.err)
+
+  def test_sign_verify_fail(self):
+    keyset_servicer = services.KeysetServicer()
+    signature_servicer = services.SignatureServicer()
+
+    template = signature.signature_key_templates.ECDSA_P256.SerializeToString()
+    gen_request = testing_api_pb2.KeysetGenerateRequest(template=template)
+    gen_response = keyset_servicer.Generate(gen_request, self._ctx)
+    self.assertEqual(gen_response.WhichOneof('result'), 'keyset')
+    self.assertEmpty(gen_response.err)
+    private_keyset = gen_response.keyset
+
+    pub_request = testing_api_pb2.KeysetPublicRequest(
+        private_keyset=private_keyset)
+    pub_response = keyset_servicer.Public(pub_request, self._ctx)
+    self.assertEqual(pub_response.WhichOneof('result'), 'public_keyset')
+    public_keyset = pub_response.public_keyset
+
+    invalid_request = testing_api_pb2.SignatureVerifyRequest(
+        public_keyset=public_keyset,
+        signature=b'invalid signature',
+        data=b'The quick brown fox jumps over the lazy dog')
+    invalid_response = signature_servicer.Verify(invalid_request, self._ctx)
+    self.assertNotEmpty(invalid_response.err)
+
+
+if __name__ == '__main__':
+  absltest.main()
diff --git a/testing/python/testing_server.py b/testing/python/testing_server.py
new file mode 100644
index 0000000..e5bfd3e
--- /dev/null
+++ b/testing/python/testing_server.py
@@ -0,0 +1,67 @@
+# 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 Primitive Testing Service in Python."""
+
+from __future__ import absolute_import
+from __future__ import division
+# Placeholder for import for type annotations
+from __future__ import print_function
+
+from concurrent import futures
+
+from absl import app
+from absl import flags
+import grpc
+from tink import aead
+from tink import daead
+from tink import hybrid
+from tink import mac
+from tink import signature
+
+from proto.testing import testing_api_pb2_grpc
+
+import services
+
+FLAGS = flags.FLAGS
+
+flags.DEFINE_integer('port', 10000, 'The port of the server.')
+
+
+def main(unused_argv):
+  aead.register()
+  daead.register()
+  hybrid.register()
+  mac.register()
+  signature.register()
+  server = grpc.server(futures.ThreadPoolExecutor(max_workers=2))
+  testing_api_pb2_grpc.add_MetadataServicer_to_server(
+      services.MetadataServicer(), server)
+  testing_api_pb2_grpc.add_KeysetServicer_to_server(
+      services.KeysetServicer(), server)
+  testing_api_pb2_grpc.add_AeadServicer_to_server(
+      services.AeadServicer(), server)
+  testing_api_pb2_grpc.add_DeterministicAeadServicer_to_server(
+      services.DeterministicAeadServicer(), server)
+  testing_api_pb2_grpc.add_MacServicer_to_server(
+      services.MacServicer(), server)
+  testing_api_pb2_grpc.add_HybridServicer_to_server(
+      services.HybridServicer(), server)
+  testing_api_pb2_grpc.add_SignatureServicer_to_server(
+      services.SignatureServicer(), server)
+  server.add_secure_port('[::]:%d' % FLAGS.port,
+                         grpc.local_server_credentials())
+  server.start()
+  server.wait_for_termination()
+
+
+if __name__ == '__main__':
+  app.run(main)
diff --git a/third_party/rules_protobuf/BUILD.bazel b/third_party/rules_protobuf/BUILD.bazel
deleted file mode 100644
index 001dcb9..0000000
--- a/third_party/rules_protobuf/BUILD.bazel
+++ /dev/null
@@ -1,5 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-exports_files(["LICENSE"])
diff --git a/third_party/rules_protobuf/LICENSE b/third_party/rules_protobuf/LICENSE
deleted file mode 100644
index d68d510..0000000
--- a/third_party/rules_protobuf/LICENSE
+++ /dev/null
@@ -1,204 +0,0 @@
-Copyright 2016 PubRef.org
-
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   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.
diff --git a/third_party/rules_protobuf/README.md b/third_party/rules_protobuf/README.md
deleted file mode 100644
index 1767faa..0000000
--- a/third_party/rules_protobuf/README.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Bazel Skylark rules for building protobufs for ObjC
-
-This is a minimal fork of [Rules Protobuf](https://github.com/pubref/rules_protobuf)
-that supports building protobuf for ObjC.
-
-Tink needs temporarily depend on these rules because objc_proto_library is
-not working properly. See https://github.com/bazelbuild/bazel/issues/1802.
-
-Once either Bazel or Protobuf team fixes objc_proto_library, these rules
-can be removed.
diff --git a/third_party/rules_protobuf/objc/BUILD.bazel b/third_party/rules_protobuf/objc/BUILD.bazel
deleted file mode 100644
index dbee277..0000000
--- a/third_party/rules_protobuf/objc/BUILD.bazel
+++ /dev/null
@@ -1,14 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
-
-load("//third_party/rules_protobuf/protobuf:rules.bzl", "proto_language")
-
-proto_language(
-    name = "objc",
-    output_file_style = "pascal",
-    pb_file_extensions = [
-        ".pbobjc.h",
-        ".pbobjc.m",
-    ],
-)
diff --git a/third_party/rules_protobuf/objc/rules.bzl b/third_party/rules_protobuf/objc/rules.bzl
deleted file mode 100644
index 69edbe1..0000000
--- a/third_party/rules_protobuf/objc/rules.bzl
+++ /dev/null
@@ -1,10 +0,0 @@
-"""Compiles protobuf for ObjC.
-
-"""
-
-load("//third_party/rules_protobuf/protobuf:rules.bzl", "proto_compile")
-
-def objc_proto_compile(
-        langs = [str(Label("//third_party/rules_protobuf/objc"))],
-        **kwargs):
-    proto_compile(langs = langs, **kwargs)
diff --git a/third_party/rules_protobuf/protobuf/BUILD.bazel b/third_party/rules_protobuf/protobuf/BUILD.bazel
deleted file mode 100644
index e6d05e7..0000000
--- a/third_party/rules_protobuf/protobuf/BUILD.bazel
+++ /dev/null
@@ -1,3 +0,0 @@
-package(default_visibility = ["//visibility:public"])
-
-licenses(["notice"])
diff --git a/third_party/rules_protobuf/protobuf/internal/proto_compile.bzl b/third_party/rules_protobuf/protobuf/internal/proto_compile.bzl
deleted file mode 100644
index c0bd552..0000000
--- a/third_party/rules_protobuf/protobuf/internal/proto_compile.bzl
+++ /dev/null
@@ -1,522 +0,0 @@
-"""Compiles protobuf.
-
-"""
-
-def _capitalize(s):
-    return s[0:1].upper() + s[1:]
-
-def _pascal_case(s):
-    return "".join([_capitalize(part) for part in s.split("_")])
-
-def _emit_params_file_action(ctx, path, mnemonic, cmds):
-    """Helper function that writes a potentially long command list to a file.
-    Args:
-      ctx (struct): The ctx object.
-      path (string): the file path where the params file should be written.
-      mnemonic (string): the action mnemomic.
-      cmds (list<string>): the command list.
-    Returns:
-      (File): an executable file that runs the command set.
-    """
-    filename = "%s.%sFile.params" % (path, mnemonic)
-    f = ctx.new_file(ctx.configuration.bin_dir, filename)
-    ctx.actions.write(
-        output = f,
-        content = "\n".join(["set -e"] + cmds),
-        is_executable = True,
-    )
-    return f
-
-def _get_relative_dirname(base, file):
-    """Return a dirname in the form of path segments relative to base.
-    If the file.short_path is not within base, return empty list.
-    Example: if base="foo/bar/baz.txt"
-             and file.short_path="bar/baz.txt",
-             return ["bar"].
-    Args:
-      base (string): the base dirname (ctx.label.package)
-      file (File): the file to calculate relative dirname.
-    Returns:
-      (list<string>): path
-    """
-    path = file.dirname
-    if not path.startswith(base):
-        return []
-    parts = path.split("/")
-    if parts[0] == "external":
-        # ignore off the first two items since we'll be cd'ing into
-        # this dir.
-        return parts[2:]
-    base_parts = base.split("/")
-    return parts[len(base_parts):]
-
-def _get_offset_path(root, path):
-    """Adjust path relative to offset"""
-
-    if path.startswith("/"):
-        fail("path argument must not be absolute: %s" % path)
-
-    if not root:
-        return path
-
-    if root == ".":
-        return path
-
-    # "external/foobar/file.proto" --> "file.proto"
-    if path.startswith(root):
-        start = len(root)
-        if not root.endswith("/"):
-            start += 1
-            return path[start:]
-
-    depth = root.count("/") + 1
-    return "../" * depth + path
-
-def _get_import_mappings_for(files, prefix, label):
-    """For a set of files that belong to the given context label, create a mapping to the given prefix."""
-
-    mappings = {}
-    for file in files:
-        src = file.short_path
-
-        # File in an external repo looks like:
-        # '../WORKSPACE/SHORT_PATH'.  We want just the SHORT_PATH.
-        if src.startswith("../"):
-            parts = src.split("/")
-            src = "/".join(parts[2:])
-        dst = [prefix, label.package]
-        name_parts = label.name.split(".")
-
-        # special case to elide last part if the name is
-        # 'go_default_library.pb'
-        if name_parts[0] != "go_default_library":
-            dst.append(name_parts[0])
-        mappings[src] = "/".join(dst)
-
-    return mappings
-
-def _build_output_jar(run, builder):
-    """Build a jar file for protoc to dump java classes into."""
-    ctx = run.ctx
-    execdir = run.data.execdir
-    name = run.lang.name
-    protojar = ctx.actions.declare_file("%s_%s.jar" % (run.data.label.name, name))
-    builder["outputs"] += [protojar]
-    builder[name + "_jar"] = protojar
-    builder[name + "_outdir"] = _get_offset_path(execdir, protojar.path)
-
-def _build_output_library(run, builder):
-    """Build a library.js file for protoc to dump java classes into."""
-    ctx = run.ctx
-    execdir = run.data.execdir
-    name = run.lang.name
-    jslib = ctx.actions.declare_file(run.data.label.name + run.lang.pb_file_extensions[0])
-    builder["jslib"] = [jslib]
-    builder["outputs"] += [jslib]
-
-    parts = jslib.short_path.rpartition("/")
-    filename = "/".join([parts[0], run.data.label.name])
-    library_path = _get_offset_path(run.data.execdir, filename)
-    builder[name + "_pb_options"] += ["library=" + library_path]
-
-def _build_output_srcjar(run, builder):
-    ctx = run.ctx
-    name = run.lang.name
-    protojar = builder[name + "_jar"]
-    srcjar_name = "%s_%s.srcjar" % (run.data.label.name, name)
-    srcjar = ctx.actions.declare_file("%s_%s.srcjar" % (run.data.label.name, name))
-    run.ctx.action(
-        mnemonic = "CpJarToSrcJar",
-        inputs = [protojar],
-        outputs = [srcjar],
-        arguments = [protojar.path, srcjar.path],
-        command = "cp $1 $2",
-    )
-
-    # Remove protojar from the list of provided outputs
-    builder["outputs"] = [e for e in builder["outputs"] if e != protojar]
-    builder["outputs"] += [srcjar]
-
-    if run.data.verbose > 2:
-        print("Copied jar %s srcjar to %s" % (protojar.path, srcjar.path))
-
-def _build_output_files(run, builder):
-    """Build a list of files we expect to be generated."""
-
-    ctx = run.ctx
-    protos = run.data.protos
-    if not protos:
-        fail("Empty proto input list.", "protos")
-
-    exts = run.exts
-
-    for file in protos:
-        base = file.basename[:-len(".proto")]
-        if run.lang.output_file_style == "pascal":
-            base = _pascal_case(base)
-        if run.lang.output_file_style == "capitalize":
-            base = _capitalize(base)
-        for ext in exts:
-            path = _get_relative_dirname(ctx.label.package, file)
-            path.append(base + ext)
-            pbfile = ctx.actions.declare_file("/".join(path))
-            builder["outputs"] += [pbfile]
-
-def _build_output_libdir(run, builder):
-    # This is currently csharp-specific, which needs to have the
-    # output_dir positively adjusted to the package directory.
-    ctx = run.ctx
-    execdir = run.data.execdir
-    name = run.lang.name
-    builder[name + "_outdir"] = _get_offset_path(execdir, run.data.descriptor_set.dirname)
-    _build_output_files(run, builder)
-
-def _build_descriptor_set(data, builder):
-    """Build a list of files we expect to be generated."""
-    builder["args"] += ["--descriptor_set_out=" + _get_offset_path(data.execdir, data.descriptor_set.path)]
-
-def _build_plugin_invocation(name, plugin, execdir, builder):
-    """Add a '--plugin=NAME=PATH' argument if the language descriptor
-    requires one.
-    """
-    tool = _get_offset_path(execdir, plugin.path)
-    builder["inputs"] += [plugin]
-    builder["args"] += ["--plugin=protoc-gen-%s=%s" % (name, tool)]
-
-def _build_protobuf_invocation(run, builder):
-    """Build a --plugin option if required for basic protobuf generation.
-    Args:
-      run (struct): the compilation run object.
-      builder (dict): the compilation builder data.
-    Built-in language don't need this.
-    """
-    lang = run.lang
-    if not lang.pb_plugin:
-        return
-    name = lang.pb_plugin_name or lang.name
-    _build_plugin_invocation(
-        name,
-        lang.pb_plugin,
-        run.data.execdir,
-        builder,
-    )
-
-def _get_mappings(files, label, prefix):
-    """For a set of files that belong the the given context label, create a mapping to the given prefix."""
-    mappings = {}
-    for file in files:
-        src = file.short_path
-
-        #print("mapping file short path: %s" % src)
-        # File in an external repo looks like:
-        # '../WORKSPACE/SHORT_PATH'.  We want just the SHORT_PATH.
-        if src.startswith("../"):
-            parts = src.split("/")
-            src = "/".join(parts[2:])
-        dst = [prefix]
-        if label.package:
-            dst.append(label.package)
-        name_parts = label.name.split(".")
-
-        # special case to elide last part if the name is
-        # 'go_default_library.pb'
-        if name_parts[0] != "go_default_library":
-            dst.append(name_parts[0])
-        mappings[src] = "/".join(dst)
-    return mappings
-
-def _build_base_namespace(run, builder):
-    pass
-
-def _build_importmappings(run, builder):
-    """Override behavior to add plugin options before building the --go_out option"""
-    ctx = run.ctx
-    go_prefix = run.data.prefix or run.lang.prefix
-    opts = []
-
-    # Build the list of import mappings.  Start with any configured on
-    # the rule by attributes.
-    mappings = run.lang.importmap + run.data.importmap
-    mappings += _get_mappings(run.data.protos, run.data.label, go_prefix)
-
-    # Then add in the transitive set from dependent rules.
-    for unit in run.data.transitive_units:
-        mappings += unit.transitive_mappings
-
-    if run.data.verbose > 1:
-        print("go_importmap: %s" % mappings)
-
-    for k, v in mappings.items():
-        opts += ["M%s=%s" % (k, v)]
-
-    builder["transitive_mappings"] = mappings
-
-def _build_plugin_out(name, outdir, options, builder):
-    """Build the --{lang}_out argument for a given plugin."""
-    arg = outdir
-    if options:
-        arg = ",".join(options) + ":" + arg
-    builder["args"] += ["--%s_out=%s" % (name, arg)]
-
-def _build_protobuf_out(run, builder):
-    """Build the --{lang}_out option"""
-    lang = run.lang
-    name = lang.pb_plugin_name or lang.name
-    outdir = builder.get(lang.name + "_outdir", run.outdir)
-    options = builder.get(lang.name + "_pb_options", [])
-
-    _build_plugin_out(name, outdir, options, builder)
-
-def _get_outdir(ctx, lang, execdir):
-    if ctx.attr.output_to_workspace:
-        outdir = "."
-    else:
-        outdir = ctx.var["GENDIR"]
-    path = _get_offset_path(execdir, outdir)
-    if execdir != ".":
-        path += "/" + execdir
-    return path
-
-def _get_external_root(ctx):
-    # Compte set of "external workspace roots" that the proto
-    # sourcefiles belong to.
-    external_roots = []
-    for file in ctx.files.protos:
-        path = file.path.split("/")
-        if path[0] == "external":
-            external_roots += ["/".join(path[0:2])]
-
-    # This set size must be 0 or 1. (all source files must exist in this
-    # workspace or the same external workspace).
-    roots = depset(external_roots)
-    n = len(roots.to_list())
-    if n:
-        if n > 1:
-            fail(
-                """
-        You are attempting simultaneous compilation of protobuf source files that span multiple workspaces (%s).
-        Decompose your library rules into smaller units having filesets that belong to only a single workspace at a time.
-        Note that it is OK to *import* across multiple workspaces, but not compile them as file inputs to protoc.
-        """ % roots,
-            )
-        else:
-            return external_roots[0]
-    else:
-        return "."
-
-def _compile(ctx, unit):
-    execdir = unit.data.execdir
-
-    protoc = _get_offset_path(execdir, unit.compiler.path)
-    imports = ["--proto_path=" + i for i in unit.imports.to_list()]
-    srcs = [_get_offset_path(execdir, p.path) for p in unit.data.protos]
-    protoc_cmd = [protoc] + unit.args.to_list() + imports + srcs
-    manifest = [f.short_path for f in unit.outputs.to_list()]
-
-    transitive_units = depset(transitive = [u.inputs for u in unit.data.transitive_units])
-    inputs = depset(transitive = [unit.inputs, transitive_units]).to_list()
-    outputs = unit.outputs.to_list()
-
-    cmds = [" ".join(protoc_cmd)]
-    if execdir != ".":
-        cmds.insert(0, "cd %s" % execdir)
-
-    if unit.data.output_to_workspace:
-        print(
-            """
->**************************************************************************
-* - Generating files into the workspace...  This is potentially           *
-*   dangerous (may overwrite existing files) and violates bazel's         *
-*   sandbox policy.                                                       *
-* - Disregard "ERROR: output 'foo.pb.*' was not created." messages.       *
-* - Build will halt following the "not all outputs were created" message. *
-* - Output manifest is printed below.                                     *
-**************************************************************************<
-%s
->*************************************************************************<
-""" % "\n".join(manifest),
-        )
-
-    if unit.data.verbose:
-        print(
-            """
-************************************************************
-cd $(bazel info execution_root)%s && \
-%s
-************************************************************
-%s
-************************************************************
-""" % (
-                "" if execdir == "." else "/" + execdir,
-                " \\ \n".join(protoc_cmd),
-                "\n".join(manifest),
-            ),
-        )
-
-    if unit.data.verbose > 2:
-        for i in range(len(protoc_cmd)):
-            print(" > cmd%s: %s" % (i, protoc_cmd[i]))
-        for i in range(len(inputs)):
-            print(" > input%s: %s" % (i, inputs[i]))
-        for i in range(len(outputs)):
-            print(" > output%s: %s" % (i, outputs[i]))
-
-    ctx.actions.run_shell(
-        mnemonic = "ProtoCompile",
-        command = " && ".join(cmds),
-        inputs = inputs,
-        outputs = outputs,
-        tools = [unit.compiler],
-    )
-
-def _proto_compile_impl(ctx):
-    if ctx.attr.verbose > 1:
-        print("proto_compile %s:%s" % (ctx.build_file_path, ctx.label.name))
-
-    # Calculate list of external roots and return the base directory
-    # we'll use for the protoc invocation.  Usually this is '.', but if
-    # not, its 'external/WORKSPACE'
-    execdir = _get_external_root(ctx)
-
-    # Propogate proto deps compilation units.
-    transitive_units = []
-    for dep in ctx.attr.deps:
-        for unit in dep.proto_compile_result.transitive_units:
-            transitive_units.append(unit)
-
-    if ctx.attr.prefix:
-        prefix = ctx.attr.prefix.go_prefix
-    else:
-        prefix = ""
-
-    # Immutable global state for this compiler run.
-    data = struct(
-        label = ctx.label,
-        workspace_name = ctx.workspace_name,
-        prefix = prefix,
-        execdir = execdir,
-        protos = ctx.files.protos,
-        descriptor_set = ctx.outputs.descriptor_set,
-        importmap = ctx.attr.importmap,
-        pb_options = ctx.attr.pb_options,
-        verbose = ctx.attr.verbose,
-        transitive_units = transitive_units,
-        output_to_workspace = ctx.attr.output_to_workspace,
-    )
-
-    #print("transitive_units: %s" % transitive_units)
-
-    # Mutable global state to be populated by the classes.
-    builder = {
-        "args": [],  # list of string
-        "imports": ctx.attr.imports + ["."],
-        "inputs": ctx.files.protos + ctx.files.inputs,
-        "outputs": [],
-    }
-
-    # Build a list of structs that will be processed in this compiler
-    # run.
-    runs = []
-    for l in ctx.attr.langs:
-        lang = l.proto_language
-
-        exts = []
-        if lang.supports_pb:
-            exts += lang.pb_file_extensions
-
-        runs.append(struct(
-            ctx = ctx,
-            outdir = _get_outdir(ctx, lang, execdir),
-            lang = lang,
-            data = data,
-            exts = exts,
-            output_to_jar = lang.output_to_jar,
-        ))
-
-        builder["inputs"] += lang.pb_inputs
-        builder["imports"] += lang.pb_imports
-        builder[lang.name + "_pb_options"] = lang.pb_options + data.pb_options
-
-    _build_descriptor_set(data, builder)
-
-    for run in runs:
-        if run.lang.output_to_jar:
-            _build_output_jar(run, builder)
-        elif run.lang.output_to_library:
-            _build_output_library(run, builder)
-        elif run.lang.output_to_libdir:
-            _build_output_libdir(run, builder)
-        else:
-            _build_output_files(run, builder)
-        if run.lang.prefix:  # golang-specific
-            _build_importmappings(run, builder)
-        if run.lang.supports_pb:
-            _build_protobuf_invocation(run, builder)
-            _build_protobuf_out(run, builder)
-
-    # Build final immutable compilation unit for rule and transitive beyond
-    unit = struct(
-        compiler = ctx.executable.protoc,
-        data = data,
-        transitive_mappings = builder.get("transitive_mappings", {}),
-        args = depset(builder["args"] + ctx.attr.args),
-        imports = depset(builder["imports"]),
-        inputs = depset(builder["inputs"]),
-        outputs = depset(builder["outputs"] + [ctx.outputs.descriptor_set]),
-    )
-
-    # Run protoc
-    _compile(ctx, unit)
-
-    for run in runs:
-        if run.lang.output_to_jar:
-            _build_output_srcjar(run, builder)
-
-    files = depset(builder["outputs"])
-
-    return struct(
-        files = files,
-        proto_compile_result = struct(
-            unit = unit,
-            transitive_units = transitive_units + [unit],
-        ),
-    )
-
-proto_compile = rule(
-    implementation = _proto_compile_impl,
-    attrs = {
-        "args": attr.string_list(),
-        "langs": attr.label_list(
-            providers = ["proto_language"],
-            allow_files = False,
-            mandatory = False,
-        ),
-        "protos": attr.label_list(
-            allow_files = [".proto"],
-        ),
-        "deps": attr.label_list(
-            providers = ["proto_compile_result"],
-        ),
-        "protoc": attr.label(
-            default = Label("@com_google_protobuf//:protoc"),
-            cfg = "host",
-            executable = True,
-        ),
-        "prefix": attr.label(
-            providers = ["go_prefix"],
-        ),
-        "root": attr.string(),
-        "imports": attr.string_list(),
-        "importmap": attr.string_dict(),
-        "inputs": attr.label_list(
-            allow_files = True,
-        ),
-        "pb_options": attr.string_list(),
-        "output_to_workspace": attr.bool(),
-        "verbose": attr.int(),
-    },
-    outputs = {
-        "descriptor_set": "%{name}.descriptor_set",
-    },
-    output_to_genfiles = True,  # this needs to be set for cc-rules.
-)
diff --git a/third_party/rules_protobuf/protobuf/internal/proto_language.bzl b/third_party/rules_protobuf/protobuf/internal/proto_language.bzl
deleted file mode 100644
index cf69f7a..0000000
--- a/third_party/rules_protobuf/protobuf/internal/proto_language.bzl
+++ /dev/null
@@ -1,92 +0,0 @@
-"""Compiles protobuf.
-
-"""
-
-def _proto_language_impl(ctx):
-    prefix = None
-    if hasattr(ctx.attr.prefix, "go_prefix"):
-        prefix = ctx.attr.prefix.go_prefix
-    return struct(
-        proto_language = struct(
-            name = ctx.label.name,
-            output_to_workspace = ctx.attr.output_to_workspace,
-            output_to_jar = ctx.attr.output_to_jar,
-            output_to_library = ctx.attr.output_to_library,
-            output_to_libdir = ctx.attr.output_to_libdir,
-            output_file_style = ctx.attr.output_file_style,
-            supports_pb = ctx.attr.supports_pb,
-            pb_file_extensions = ctx.attr.pb_file_extensions,
-            pb_options = ctx.attr.pb_options,
-            pb_imports = ctx.attr.pb_imports,
-            pb_inputs = ctx.files.pb_inputs,
-            pb_plugin_name = ctx.attr.pb_plugin_name,
-            pb_plugin = ctx.executable.pb_plugin,
-            pb_compile_deps = ctx.files.pb_compile_deps,
-            pb_runtime_deps = ctx.files.pb_runtime_deps,
-            prefix = prefix,
-            importmap = ctx.attr.importmap,
-        ),
-    )
-
-proto_language_attrs = {
-    "output_to_workspace": attr.bool(),
-    "output_to_jar": attr.bool(),
-    "output_to_library": attr.bool(),
-    "output_to_libdir": attr.bool(),
-    "output_file_style": attr.string(),
-    "supports_pb": attr.bool(default = True),
-    "pb_file_extensions": attr.string_list(),
-    "pb_options": attr.string_list(),
-    "pb_inputs": attr.label_list(),
-    "pb_imports": attr.string_list(),
-    "pb_plugin_name": attr.string(),
-    "pb_plugin": attr.label(
-        executable = True,
-        cfg = "host",
-    ),
-    "pb_compile_deps": attr.label_list(),
-    "pb_runtime_deps": attr.label_list(),
-    "prefix": attr.label(
-        providers = ["go_prefix"],
-    ),
-    "importmap": attr.string_dict(),
-}
-
-proto_language = rule(
-    implementation = _proto_language_impl,
-    attrs = proto_language_attrs,
-)
-
-def _proto_language_deps_impl(ctx):
-    files = []
-    exts = ctx.attr.file_extensions
-
-    for dep in ctx.attr.langs:
-        lang = dep.proto_language
-        if ctx.attr.compile_deps:
-            files += lang.pb_compile_deps
-        if ctx.attr.runtime_deps:
-            files += lang.pb_runtime_deps
-
-    deps = []
-    for file in files:
-        for ext in exts:
-            if file.path.endswith(ext):
-                deps.append(file)
-
-    return struct(
-        files = depset(deps),
-    )
-
-proto_language_deps = rule(
-    implementation = _proto_language_deps_impl,
-    attrs = {
-        "langs": attr.label_list(
-            providers = ["proto_language"],
-            mandatory = True,
-        ),
-        "file_extensions": attr.string_list(mandatory = True),
-        "compile_deps": attr.bool(default = True),
-        "runtime_deps": attr.bool(default = False),
-    },
-)
diff --git a/third_party/rules_protobuf/protobuf/rules.bzl b/third_party/rules_protobuf/protobuf/rules.bzl
deleted file mode 100644
index ecaaf19..0000000
--- a/third_party/rules_protobuf/protobuf/rules.bzl
+++ /dev/null
@@ -1,17 +0,0 @@
-"""Compiles protobuf for ObjC.
-
-"""
-
-load(
-    "//third_party/rules_protobuf/protobuf:internal/proto_compile.bzl",
-    _proto_compile = "proto_compile",
-)
-load(
-    "//third_party/rules_protobuf/protobuf:internal/proto_language.bzl",
-    _proto_language = "proto_language",
-    _proto_language_deps = "proto_language_deps",
-)
-
-proto_compile = _proto_compile
-proto_language = _proto_language
-proto_language_deps = _proto_language_deps
diff --git a/tink_base_deps.bzl b/tink_base_deps.bzl
index c434913..9b75246 100644
--- a/tink_base_deps.bzl
+++ b/tink_base_deps.bzl
@@ -48,7 +48,7 @@
             name = "google_root_pem",
             executable = 0,
             urls = ["https://pki.goog/roots.pem"],
-            sha256 = "7f03c894282e3fc39105466a8ee5055ffd05e79dfd4010360117078afbfa68bd",
+            sha256 = "6b1ad80fb0b67022b23a965429740c7643c50583d0f16e0a9f1357cd16d2255a",
         )
 
     # proto
@@ -67,14 +67,14 @@
 
     # Remote Build Execution
     if not native.existing_rule("bazel_toolchains"):
-        # Latest bazel_toolchains package on 2020-02-21
+        # Latest bazel_toolchains package on 2020-06-02
         http_archive(
             name = "bazel_toolchains",
-            sha256 = "4d348abfaddbcee0c077fc51bb1177065c3663191588ab3d958f027cbfe1818b",
-            strip_prefix = "bazel-toolchains-2.1.0",
+            sha256 = "db48eed61552e25d36fe051a65d2a329cc0fb08442627e8f13960c5ab087a44e",
+            strip_prefix = "bazel-toolchains-3.2.0",
             urls = [
-                "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/2.1.0.tar.gz",
-                "https://github.com/bazelbuild/bazel-toolchains/archive/2.1.0.tar.gz",
+                "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/releases/download/3.2.0/bazel-toolchains-3.2.0.tar.gz",
+                "https://github.com/bazelbuild/bazel-toolchains/archive/3.2.0.tar.gz",
             ],
         )
     if not native.existing_rule("wycheproof"):
diff --git a/tools/objc.bzl b/tools/objc.bzl
deleted file mode 100644
index 77588b9..0000000
--- a/tools/objc.bzl
+++ /dev/null
@@ -1,68 +0,0 @@
-# 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.
-
-"""Compiles protobuf for ObjC.
-
-This tool uses https://github.com/pubref/rules_protobuf.
-"""
-
-# The actual rule which does the filtering.
-def _do_filter_impl(ctx):
-    return struct(
-        files = depset([f for f in ctx.files.srcs if f.path.endswith(ctx.attr.suffix)]),
-    )
-
-_do_filter = rule(
-    attrs = {
-        "srcs": attr.label_list(
-            mandatory = True,
-            allow_files = True,
-        ),
-        "suffix": attr.string(
-            mandatory = True,
-        ),
-    },
-    implementation = _do_filter_impl,
-)
-
-# A convenient macro to wrap the custom rule and objc_library.
-def tink_objc_proto_library(name, srcs, **kwargs):
-    """
-    Compiles ObjC proto libaries in srcs into a single library.
-
-    Args:
-      name: the name of the output library
-      srcs: the list of ObjC proto libraries, which are generated using
-            objc_proto_compile in rules_protobuf.
-    """
-
-    _do_filter(
-        name = "%s_hdrs" % name,
-        visibility = ["//visibility:private"],
-        # srcs = hdrs,
-        srcs = srcs,
-        suffix = ".pbobjc.h",
-    )
-    _do_filter(
-        name = "%s_srcs" % name,
-        visibility = ["//visibility:private"],
-        srcs = srcs,
-        suffix = ".pbobjc.m",
-    )
-    native.objc_library(
-        name = name,
-        srcs = [":%s_srcs" % name],
-        hdrs = [":%s_hdrs" % name],
-        copts = ["-fno-objc-arc"],
-        deps = ["@com_google_protobuf//:objectivec"],
-        **kwargs
-    )
diff --git a/tools/testing/BUILD.bazel b/tools/testing/BUILD.bazel
index 2573616..7061c26 100644
--- a/tools/testing/BUILD.bazel
+++ b/tools/testing/BUILD.bazel
@@ -1,9 +1,9 @@
+load("@tink_java//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+
 package(default_visibility = ["//:__subpackages__"])
 
 licenses(["notice"])
 
-load("@tink_java//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
-
 java_library(
     name = "cli_util",
     testonly = 1,
@@ -12,7 +12,11 @@
     ],
     javacopts = JAVACOPTS_OSS,
     deps = [
-        "@tink_java//:testonly",
+        "@tink_java//src/main/java/com/google/crypto/tink:binary_keyset_reader",
+        "@tink_java//src/main/java/com/google/crypto/tink:binary_keyset_writer",
+        "@tink_java//src/main/java/com/google/crypto/tink:cleartext_keyset_handle",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
+        "@tink_java//src/main/java/com/google/crypto/tink/config:tink_config",
     ],
 )
 
@@ -22,8 +26,8 @@
     srcs = ["java/com/google/crypto/tink/testing/CompareKeysets.java"],
     javacopts = JAVACOPTS_OSS,
     deps = [
-        "@tink_java//src/main/java/com/google/crypto/tink:privileged_registry",
         "@tink_java//proto:tink_java_proto",
+        "@tink_java//src/main/java/com/google/crypto/tink:privileged_registry",
     ],
 )
 
@@ -37,8 +41,8 @@
         ":cli_util",
         ":compare_keysets",
         "@tink_java//:cleartext_keyset_handle",
-        "@tink_java//src/main/java/com/google/crypto/tink:core",
-        "@tink_java//src/main/java/com/google/crypto/tink/config",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
+        "@tink_java//src/main/java/com/google/crypto/tink/config:tink_config",
     ],
 )
 
@@ -51,7 +55,7 @@
     javacopts = JAVACOPTS_OSS,
     main_class = "com.google.crypto.tink.testing.VersionCli",
     deps = [
-        "@tink_java//:testonly",
+        "@tink_java//src/main/java/com/google/crypto/tink:core",
     ],
 )
 
@@ -65,12 +69,14 @@
     main_class = "com.google.crypto.tink.testing.AeadCli",
     deps = [
         ":cli_util",
-        "@tink_java//:awskms",
-        "@tink_java//:gcpkms",
-        "@tink_java//:testonly",
-        "@tink_java//:testutil",
-        "@tink_java//src/main/java/com/google/crypto/tink",
-        "@tink_java//src/main/java/com/google/crypto/tink/aead",
+        "@tink_java//src/main/java/com/google/crypto/tink:aead",
+        "@tink_java//src/main/java/com/google/crypto/tink:kms_client",
+        "@tink_java//src/main/java/com/google/crypto/tink:kms_clients",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
+        "@tink_java//src/main/java/com/google/crypto/tink/aead:aead_config",
+        "@tink_java//src/main/java/com/google/crypto/tink/integration/awskms:aws_kms_client",
+        "@tink_java//src/main/java/com/google/crypto/tink/integration/gcpkms:gcp_kms_client",
+        "@tink_java//src/main/java/com/google/crypto/tink/testing:test_util",
     ],
 )
 
@@ -84,7 +90,8 @@
     main_class = "com.google.crypto.tink.testing.DeterministicAeadCli",
     deps = [
         ":cli_util",
-        "@tink_java//:testonly",
+        "@tink_java//src/main/java/com/google/crypto/tink:deterministic_aead",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
     ],
 )
 
@@ -98,7 +105,8 @@
     main_class = "com.google.crypto.tink.testing.StreamingAeadCli",
     deps = [
         ":cli_util",
-        "@tink_java//:testonly",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
+        "@tink_java//src/main/java/com/google/crypto/tink:streaming_aead",
     ],
 )
 
@@ -112,7 +120,8 @@
     main_class = "com.google.crypto.tink.testing.MacCli",
     deps = [
         ":cli_util",
-        "@tink_java//:testonly",
+        "@tink_java//src/main/java/com/google/crypto/tink:mac",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
     ],
 )
 
@@ -126,7 +135,8 @@
     main_class = "com.google.crypto.tink.testing.HybridEncryptCli",
     deps = [
         ":cli_util",
-        "@tink_java//:testonly",
+        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_encrypt",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
     ],
 )
 
@@ -140,7 +150,8 @@
     main_class = "com.google.crypto.tink.testing.HybridDecryptCli",
     deps = [
         ":cli_util",
-        "@tink_java//:testonly",
+        "@tink_java//src/main/java/com/google/crypto/tink:hybrid_decrypt",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
     ],
 )
 
@@ -154,7 +165,8 @@
     main_class = "com.google.crypto.tink.testing.PublicKeySignCli",
     deps = [
         ":cli_util",
-        "@tink_java//:testonly",
+        "@tink_java//src/main/java/com/google/crypto/tink:public_key_sign",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
     ],
 )
 
@@ -168,20 +180,7 @@
     main_class = "com.google.crypto.tink.testing.PublicKeyVerifyCli",
     deps = [
         ":cli_util",
-        "@tink_java//:testonly",
-    ],
-)
-
-py_library(
-    name = "supported_key_types",
-    testonly = 1,
-    srcs = ["supported_key_types.py"],
-    deps = [
-        "@tink_py//tink/aead",
-        "@tink_py//tink/daead",
-        "@tink_py//tink/hybrid",
-        "@tink_py//tink/mac",
-        "@tink_py//tink/signature",
-        "@tink_py//tink/proto:tink_py_pb2",
+        "@tink_java//src/main/java/com/google/crypto/tink:public_key_verify",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
     ],
 )
diff --git a/tools/testing/cross_language/BUILD.bazel b/tools/testing/cross_language/BUILD.bazel
index 5a34468..321073f 100644
--- a/tools/testing/cross_language/BUILD.bazel
+++ b/tools/testing/cross_language/BUILD.bazel
@@ -13,19 +13,6 @@
 )
 
 sh_test(
-    name = "keyset_reader_writer_test",
-    size = "small",
-    srcs = [
-        "keyset_reader_writer_test.sh",
-    ],
-    data = [
-        ":test_lib",
-        "//testing/cc:keyset_reader_writer_cli",
-        "//tinkey",
-    ],
-)
-
-sh_test(
     name = "version_test",
     size = "medium",
     srcs = [
@@ -39,21 +26,6 @@
     ],
 )
 
-py_test(
-    name = "aead_test",
-    srcs = ["aead_test.py"],
-    python_version = "PY3",
-    srcs_version = "PY3",
-    deps = [
-        requirement("absl-py"),
-        "//testing:supported_key_types",
-        "//testing/cross_language/util:cli_aead",
-        "//testing/cross_language/util:keyset_manager",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/aead",
-    ],
-)
-
 sh_test(
     name = "aead_envelope_test",
     size = "medium",
@@ -74,21 +46,6 @@
     tags = ["no_rbe"],
 )
 
-py_test(
-    name = "deterministic_aead_test",
-    srcs = ["deterministic_aead_test.py"],
-    python_version = "PY3",
-    srcs_version = "PY3",
-    deps = [
-        requirement("absl-py"),
-        "//testing:supported_key_types",
-        "//testing/cross_language/util:cli_daead",
-        "//testing/cross_language/util:keyset_manager",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/daead",
-    ],
-)
-
 sh_test(
     name = "streaming_aead_test",
     size = "medium",
@@ -104,21 +61,6 @@
     ],
 )
 
-py_test(
-    name = "mac_test",
-    srcs = ["mac_test.py"],
-    python_version = "PY3",
-    srcs_version = "PY3",
-    deps = [
-        requirement("absl-py"),
-        "//testing:supported_key_types",
-        "//testing/cross_language/util:cli_mac",
-        "//testing/cross_language/util:keyset_manager",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/mac",
-    ],
-)
-
 sh_test(
     name = "prf_set_test",
     size = "medium",
@@ -132,33 +74,3 @@
         "//tinkey",
     ],
 )
-
-py_test(
-    name = "hybrid_encryption_test",
-    srcs = ["hybrid_encryption_test.py"],
-    python_version = "PY3",
-    srcs_version = "PY3",
-    deps = [
-        requirement("absl-py"),
-        "//testing:supported_key_types",
-        "//testing/cross_language/util:cli_hybrid",
-        "//testing/cross_language/util:keyset_manager",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/hybrid",
-    ],
-)
-
-py_test(
-    name = "signature_test",
-    srcs = ["signature_test.py"],
-    python_version = "PY3",
-    srcs_version = "PY3",
-    deps = [
-        requirement("absl-py"),
-        "//testing:supported_key_types",
-        "//testing/cross_language/util:cli_signature",
-        "//testing/cross_language/util:keyset_manager",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/signature",
-    ],
-)
diff --git a/tools/testing/cross_language/aead_test.py b/tools/testing/cross_language/aead_test.py
deleted file mode 100644
index 6a29eed..0000000
--- a/tools/testing/cross_language/aead_test.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# 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.
-"""Cross-language tests for the Aead primitive."""
-
-from absl.testing import absltest
-from absl.testing import parameterized
-
-import tink
-from tink import aead
-
-from tools.testing import supported_key_types
-from tools.testing.cross_language.util import cli_aead
-from tools.testing.cross_language.util import keyset_manager
-
-
-def setUpModule():
-  aead.register()
-
-
-class AeadPythonTest(parameterized.TestCase):
-
-  @parameterized.parameters(
-      supported_key_types.test_cases(supported_key_types.AEAD_KEY_TYPES))
-  def test_encrypt_decrypt(self, key_template_name, supported_langs):
-    key_template = supported_key_types.KEY_TEMPLATE[key_template_name]
-    keyset_handle = keyset_manager.new_keyset_handle(key_template)
-    supported_aeads = [
-        cli_aead.CliAead(lang, keyset_handle) for lang in supported_langs
-    ]
-    unsupported_aeads = [
-        cli_aead.CliAead(lang, keyset_handle)
-        for lang in cli_aead.LANGUAGES
-        if lang not in supported_langs
-    ]
-    for p in supported_aeads:
-      plaintext = (
-          b'This is some plaintext message to be encrypted using key_template '
-          b'%s using %s for encryption.'
-          % (key_template_name.encode('utf8'), p.lang.encode('utf8')))
-      associated_data = (
-          b'Some associated data for %s using %s for encryption.' %
-          (key_template_name.encode('utf8'), p.lang.encode('utf8')))
-      ciphertext = p.encrypt(plaintext, associated_data)
-      for p2 in supported_aeads:
-        output = p2.decrypt(ciphertext, associated_data)
-        self.assertEqual(output, plaintext)
-      for p2 in unsupported_aeads:
-        with self.assertRaises(tink.TinkError):
-          p2.decrypt(ciphertext, associated_data)
-    for p in unsupported_aeads:
-      with self.assertRaises(tink.TinkError):
-        p.encrypt(b'plaintext', b'associated_data')
-
-if __name__ == '__main__':
-  absltest.main()
diff --git a/tools/testing/cross_language/keyset_reader_writer_test.sh b/tools/testing/cross_language/keyset_reader_writer_test.sh
deleted file mode 100755
index 2d25ae9..0000000
--- a/tools/testing/cross_language/keyset_reader_writer_test.sh
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/bin/bash
-# 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.
-################################################################################
-
-
-ROOT_DIR="$TEST_SRCDIR/tools"
-CC_KEYSET_RW_CLI="$ROOT_DIR/testing/cc/keyset_reader_writer_cli"
-JAVA_KEYSET_RW_CLI="$ROOT_DIR/tinkey/tinkey"
-TEST_UTIL="$ROOT_DIR/testing/cross_language/test_util.sh"
-
-source $TEST_UTIL || exit 1
-
-#############################################################################
-### Helpers for KeysetReader and KeysetWriter-tests.
-
-# Checks cross-language compatibility of implementations of
-# KeysetReader and KeysetWriter interfaces.
-#
-# Expects as parameter an input keyset in format given by 'input_format'
-# (generated by the corresponding Java KeysetWriter).
-# Uses CC_KEYSET_RW_CLI (which is based on C++ KeysetReader/Writer)
-# to read the input and output a copy of the input keyst in format
-# specified in 'output_format'.
-keyset_reader_writer_basic_test() {
-  local test_name="keyset-reader-writer-basic-test"
-  local input_format="$1"
-  local input_file="$2"
-  local output_format="$3"
-
-  local test_instance="${test_name}_${input_format}_${output_format}"
-
-  echo "############ starting test $test_instance for keyset file"
-  local output_file="$TEST_TMPDIR/${test_instance}_output.${output_format}"
-  $CC_KEYSET_RW_CLI $input_format $input_file $output_format $output_file
-
-  # Use binary representation for output comparison, as JSON representation
-  # seems not to be deterministic, and the order of fields varies.
-  local orig_java_keyset="$TEST_TMPDIR/${test_instance}_orig_keyset_java.bin"
-  local copy_cc_keyset="$TEST_TMPDIR/${test_instance}_copy_keyset_cc.bin"
-  echo "### generating JSON representation of the original keyset"
-  $JAVA_KEYSET_RW_CLI convert-keyset --in-format $input_format\
-    --in $input_file --out-format "BINARY" > $orig_java_keyset
-  echo "### generating JSON representation of the copied keyset"
-  $JAVA_KEYSET_RW_CLI convert-keyset --in-format $output_format\
-    --in $output_file --out-format "BINARY" > $copy_cc_keyset
-  assert_files_equal $orig_java_keyset $copy_cc_keyset
-}
-
-#############################################################################
-##### Run the actual tests.
-
-### Java BINARY, C++ BINARY
-generate_symmetric_key "AEAD-test-1" "AES128_CTR_HMAC_SHA256" "BINARY"
-keyset_reader_writer_basic_test "BINARY" $symmetric_key_file "BINARY"
-
-### Java BINARY, C++ JSON
-generate_symmetric_key "AEAD-test-2" "AES128_CTR_HMAC_SHA256" "BINARY"
-keyset_reader_writer_basic_test "BINARY" $symmetric_key_file "JSON"
-
-### Java JSON, C++ BINARY
-generate_symmetric_key "AEAD-test-3" "AES128_CTR_HMAC_SHA256" "JSON"
-keyset_reader_writer_basic_test "JSON" $symmetric_key_file "BINARY"
-
-### Java JSON, C++ JSON
-generate_symmetric_key "AEAD-test-4" "AES256_CTR_HMAC_SHA256" "JSON"
-keyset_reader_writer_basic_test "JSON" $symmetric_key_file "JSON"
diff --git a/tools/testing/cross_language/util/BUILD.bazel b/tools/testing/cross_language/util/BUILD.bazel
deleted file mode 100644
index f5fbd28..0000000
--- a/tools/testing/cross_language/util/BUILD.bazel
+++ /dev/null
@@ -1,209 +0,0 @@
-load("@rules_python//python:defs.bzl", "py_library")
-load("@pip_deps//:requirements.bzl", "requirement")
-
-package(default_visibility = ["//visibility:public"])
-
-py_library(
-    name = "cli_aead",
-    testonly = 1,
-    srcs = [
-        "cli_aead.py",
-    ],
-    data = [
-        "//testing:aead_cli_java",
-        "//testing/cc:aead_cli_cc",
-        "//testing/go:aead_cli_go",
-        "//testing/python:aead_cli_python",
-    ],
-    deps = [
-        "@tink_py//tink:cleartext_keyset_handle",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/aead",
-    ],
-)
-
-py_test(
-    name = "cli_aead_test",
-    srcs = ["cli_aead_test.py"],
-    srcs_version = "PY3",
-    deps = [
-        ":cli_aead",
-        requirement("absl-py"),
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/aead",
-    ],
-)
-
-py_library(
-    name = "cli_daead",
-    testonly = 1,
-    srcs = [
-        "cli_daead.py",
-    ],
-    data = [
-        "//testing:deterministic_aead_cli_java",
-        "//testing/cc:deterministic_aead_cli_cc",
-        "//testing/go:deterministic_aead_cli_go",
-        "//testing/python:deterministic_aead_cli_python",
-    ],
-    deps = [
-        "@tink_py//tink:cleartext_keyset_handle",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/daead",
-    ],
-)
-
-py_test(
-    name = "cli_daead_test",
-    srcs = ["cli_daead_test.py"],
-    srcs_version = "PY3",
-    deps = [
-        ":cli_daead",
-        requirement("absl-py"),
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/daead",
-    ],
-)
-
-py_library(
-    name = "cli_hybrid",
-    testonly = 1,
-    srcs = [
-        "cli_hybrid.py",
-    ],
-    data = [
-        "//testing:hybrid_decrypt_cli_java",
-        "//testing:hybrid_encrypt_cli_java",
-        "//testing/cc:hybrid_decrypt_cli_cc",
-        "//testing/cc:hybrid_encrypt_cli_cc",
-        "//testing/go:hybrid_decrypt_cli_go",
-        "//testing/go:hybrid_encrypt_cli_go",
-        "//testing/python:hybrid_decrypt_cli_python",
-        "//testing/python:hybrid_encrypt_cli_python",
-    ],
-    deps = [
-        "@tink_py//tink:cleartext_keyset_handle",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/hybrid",
-    ],
-)
-
-py_test(
-    name = "cli_hybrid_test",
-    srcs = ["cli_hybrid_test.py"],
-    srcs_version = "PY3",
-    deps = [
-        ":cli_hybrid",
-        requirement("absl-py"),
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/hybrid",
-    ],
-)
-
-py_library(
-    name = "cli_mac",
-    testonly = 1,
-    srcs = [
-        "cli_mac.py",
-    ],
-    data = [
-        "//testing:mac_cli_java",
-        "//testing/cc:mac_cli_cc",
-        "//testing/go:mac_cli_go",
-        "//testing/python:mac_cli_python",
-    ],
-    deps = [
-        "@tink_py//tink:cleartext_keyset_handle",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/mac",
-    ],
-)
-
-py_test(
-    name = "cli_mac_test",
-    srcs = ["cli_mac_test.py"],
-    srcs_version = "PY3",
-    deps = [
-        ":cli_mac",
-        requirement("absl-py"),
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/mac",
-    ],
-)
-
-py_library(
-    name = "cli_signature",
-    testonly = 1,
-    srcs = [
-        "cli_signature.py",
-    ],
-    data = [
-        "//testing:public_key_sign_cli_java",
-        "//testing:public_key_verify_cli_java",
-        "//testing/cc:public_key_sign_cli_cc",
-        "//testing/cc:public_key_verify_cli_cc",
-        "//testing/go:public_key_sign_cli_go",
-        "//testing/go:public_key_verify_cli_go",
-        "//testing/python:public_key_sign_cli_python",
-        "//testing/python:public_key_verify_cli_python",
-    ],
-    deps = [
-        "@tink_py//tink:cleartext_keyset_handle",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/mac",
-    ],
-)
-
-py_test(
-    name = "cli_signature_test",
-    srcs = ["cli_signature_test.py"],
-    srcs_version = "PY3",
-    deps = [
-        ":cli_signature",
-        requirement("absl-py"),
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/signature",
-    ],
-)
-
-py_library(
-    name = "cli_tinkey",
-    testonly = 1,
-    srcs = [
-        "cli_tinkey.py",
-    ],
-    data = [
-        "//tinkey",
-    ],
-    deps = [
-        "@tink_py//tink:cleartext_keyset_handle",
-        "@tink_py//tink:tink_python",
-    ],
-)
-
-py_test(
-    name = "cli_tinkey_test",
-    srcs = ["cli_tinkey_test.py"],
-    srcs_version = "PY3",
-    deps = [
-        ":cli_aead",
-        ":cli_daead",
-        ":cli_hybrid",
-        ":cli_mac",
-        ":cli_tinkey",
-        requirement("absl-py"),
-    ],
-)
-
-py_library(
-    name = "keyset_manager",
-    testonly = 1,
-    srcs = [
-        "keyset_manager.py",
-    ],
-    deps = [
-        ":cli_tinkey",
-        "@tink_py//tink:tink_python",
-        "@tink_py//tink/proto:tink_py_pb2",
-    ],
-)
diff --git a/tools/testing/cross_language/util/cli_aead.py b/tools/testing/cross_language/util/cli_aead.py
deleted file mode 100644
index a815449..0000000
--- a/tools/testing/cross_language/util/cli_aead.py
+++ /dev/null
@@ -1,81 +0,0 @@
-# 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.
-"""Wraps an AEAD CLI into a Python Tink Aead class."""
-
-# Placeholder for import for type annotations
-
-import os
-import subprocess
-import tempfile
-
-import tink
-from tink import aead
-from tink import cleartext_keyset_handle
-
-from typing import Text
-
-
-# All languages that have an AEAD CLI.
-LANGUAGES = ('cc', 'go', 'java', 'python')
-
-# Path are relative to tools directory.
-_AEAD_CLI_PATHS = {
-    'cc': 'testing/cc/aead_cli_cc',
-    'go': 'testing/go/aead_cli_go',
-    'java': 'testing/aead_cli_java',
-    'python': 'testing/python/aead_cli_python',
-}
-
-
-def _tools_path() -> Text:
-  util_path = os.path.dirname(os.path.abspath(__file__))
-  return os.path.dirname(os.path.dirname(os.path.dirname(util_path)))
-
-
-class CliAead(aead.Aead):
-  """Wraps a AEAD CLI binary into a Python AEAD primitive."""
-
-  def __init__(self, lang: Text, keyset_handle: tink.KeysetHandle) -> None:
-    self.lang = lang
-    self._cli = os.path.join(_tools_path(), _AEAD_CLI_PATHS[lang])
-    self._keyset_handle = keyset_handle
-
-  def _run(self, operation: Text, input_data: bytes,
-           associated_data: bytes) -> bytes:
-    with tempfile.TemporaryDirectory() as tmpdir:
-      keyset_filename = os.path.join(tmpdir, 'keyset_file')
-      input_filename = os.path.join(tmpdir, 'input_file')
-      associated_data_filename = os.path.join(tmpdir, 'associated_data_file')
-      output_filename = os.path.join(tmpdir, 'output_file')
-      with open(keyset_filename, 'wb') as f:
-        cleartext_keyset_handle.write(
-            tink.BinaryKeysetWriter(f), self._keyset_handle)
-      with open(input_filename, 'wb') as f:
-        f.write(input_data)
-      with open(associated_data_filename, 'wb') as f:
-        f.write(associated_data)
-      try:
-        unused_return_value = subprocess.check_output([
-            self._cli, keyset_filename, operation,
-            input_filename, associated_data_filename, output_filename
-        ])
-      except subprocess.CalledProcessError as e:
-        raise tink.TinkError(e)
-      with open(output_filename, 'rb') as f:
-        output_data = f.read()
-    return output_data
-
-  def encrypt(self, plaintext: bytes, associated_data: bytes) -> bytes:
-    return self._run('encrypt', plaintext, associated_data)
-
-  def decrypt(self, ciphertext: bytes, associated_data: bytes) -> bytes:
-    return self._run('decrypt', ciphertext, associated_data)
diff --git a/tools/testing/cross_language/util/cli_aead_test.py b/tools/testing/cross_language/util/cli_aead_test.py
deleted file mode 100644
index c56759c..0000000
--- a/tools/testing/cross_language/util/cli_aead_test.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# 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 tink.tools.testing.cross_language.util.cli_aead."""
-
-from absl.testing import absltest
-from absl.testing import parameterized
-import tink
-from tink import aead
-from tools.testing.cross_language.util import cli_aead
-
-
-def setUpModule():
-  aead.register()
-
-
-class CliAeadTest(parameterized.TestCase):
-
-  @parameterized.parameters(*cli_aead.LANGUAGES)
-  def test_encrypt_decrypt_success(self, lang):
-    keyset_handle = tink.new_keyset_handle(
-        aead.aead_key_templates.AES128_GCM)
-    primitive = cli_aead.CliAead(lang, keyset_handle)
-    plaintext = b'plaintext'
-    associated_data = b'associated_data'
-    ciphertext = primitive.encrypt(plaintext, associated_data)
-    output = primitive.decrypt(ciphertext, associated_data)
-    self.assertEqual(output, plaintext)
-
-  @parameterized.parameters(*cli_aead.LANGUAGES)
-  def test_invalid_decrypt_raises_error(self, lang):
-    keyset_handle = tink.new_keyset_handle(
-        aead.aead_key_templates.AES128_GCM)
-    primitive = cli_aead.CliAead(lang, keyset_handle)
-    with self.assertRaises(tink.TinkError):
-      primitive.decrypt(b'invalid ciphertext', b'associated_data')
-
-
-if __name__ == '__main__':
-  absltest.main()
diff --git a/tools/testing/cross_language/util/cli_daead.py b/tools/testing/cross_language/util/cli_daead.py
deleted file mode 100644
index d1bc265..0000000
--- a/tools/testing/cross_language/util/cli_daead.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# 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.
-"""Wraps a Deterministic AEAD CLI into a Python Tink DeterministicAead class."""
-
-# Placeholder for import for type annotations
-
-import os
-import subprocess
-import tempfile
-
-import tink
-from tink import cleartext_keyset_handle
-from tink import daead
-
-from typing import Text
-
-# All languages that have an Deterministic AEAD CLI.
-LANGUAGES = ('cc', 'go', 'java', 'python')
-
-# Path are relative to tools directory.
-_DAEAD_CLI_PATHS = {
-    'cc': 'testing/cc/deterministic_aead_cli_cc',
-    'go': 'testing/go/deterministic_aead_cli_go',
-    'java': 'testing/deterministic_aead_cli_java',
-    'python': 'testing/python/deterministic_aead_cli_python',
-}
-
-
-def _tools_path() -> Text:
-  util_path = os.path.dirname(os.path.abspath(__file__))
-  return os.path.dirname(os.path.dirname(os.path.dirname(util_path)))
-
-
-class CliDeterministicAead(daead.DeterministicAead):
-  """Wraps Deterministic AEAD CLI binary into a DeterministicAead primitive."""
-
-  def __init__(self, lang: Text, keyset_handle: tink.KeysetHandle) -> None:
-    self.lang = lang
-    self._cli = os.path.join(_tools_path(), _DAEAD_CLI_PATHS[lang])
-    self._keyset_handle = keyset_handle
-
-  def _run(self, operation: Text, input_data: bytes,
-           associated_data: bytes) -> bytes:
-    with tempfile.TemporaryDirectory() as tmpdir:
-      keyset_filename = os.path.join(tmpdir, 'keyset_file')
-      input_filename = os.path.join(tmpdir, 'input_file')
-      associated_data_filename = os.path.join(tmpdir, 'associated_data_file')
-      output_filename = os.path.join(tmpdir, 'output_file')
-      with open(keyset_filename, 'wb') as f:
-        cleartext_keyset_handle.write(
-            tink.BinaryKeysetWriter(f), self._keyset_handle)
-      with open(input_filename, 'wb') as f:
-        f.write(input_data)
-      with open(associated_data_filename, 'wb') as f:
-        f.write(associated_data)
-      try:
-        unused_return_value = subprocess.check_output([
-            self._cli, keyset_filename, operation,
-            input_filename, associated_data_filename, output_filename
-        ])
-      except subprocess.CalledProcessError as e:
-        raise tink.TinkError(e)
-      with open(output_filename, 'rb') as f:
-        output_data = f.read()
-      return output_data
-
-  def encrypt_deterministically(
-      self, plaintext: bytes, associated_data: bytes) -> bytes:
-    return self._run('encryptdeterministically', plaintext, associated_data)
-
-  def decrypt_deterministically(
-      self, ciphertext: bytes, associated_data: bytes) -> bytes:
-    return self._run('decryptdeterministically', ciphertext, associated_data)
-
diff --git a/tools/testing/cross_language/util/cli_daead_test.py b/tools/testing/cross_language/util/cli_daead_test.py
deleted file mode 100644
index 7d7d9a9..0000000
--- a/tools/testing/cross_language/util/cli_daead_test.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# 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 tink.tools.testing.cross_language.util.cli_daead."""
-
-from absl.testing import absltest
-from absl.testing import parameterized
-
-import tink
-from tink import daead
-
-from tools.testing.cross_language.util import cli_daead
-
-
-def setUpModule():
-  daead.register()
-
-
-class CliDaeadTest(parameterized.TestCase):
-
-  @parameterized.parameters(*cli_daead.LANGUAGES)
-  def test_encrypt_decrypt_success(self, lang):
-    keyset_handle = tink.new_keyset_handle(
-        daead.deterministic_aead_key_templates.AES256_SIV)
-    p = cli_daead.CliDeterministicAead(lang, keyset_handle)
-    plaintext = b'plaintext'
-    associated_data = b'associated_data'
-    ciphertext = p.encrypt_deterministically(plaintext, associated_data)
-    output = p.decrypt_deterministically(ciphertext, associated_data)
-    self.assertEqual(output, plaintext)
-
-  @parameterized.parameters(*cli_daead.LANGUAGES)
-  def test_invalid_decrypt_raises_error(self, lang):
-    keyset_handle = tink.new_keyset_handle(
-        daead.deterministic_aead_key_templates.AES256_SIV)
-    p = cli_daead.CliDeterministicAead(lang, keyset_handle)
-    with self.assertRaises(tink.TinkError):
-      p.decrypt_deterministically(b'invalid ciphertext', b'associated_data')
-
-
-if __name__ == '__main__':
-  absltest.main()
diff --git a/tools/testing/cross_language/util/cli_hybrid.py b/tools/testing/cross_language/util/cli_hybrid.py
deleted file mode 100644
index 1d3e597..0000000
--- a/tools/testing/cross_language/util/cli_hybrid.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# 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.
-"""Wraps Hybrid Encryption CLIs into a Python Tink classes."""
-
-# Placeholder for import for type annotations
-
-import os
-import subprocess
-import tempfile
-
-import tink
-from tink import cleartext_keyset_handle
-from tink import hybrid
-
-from typing import Text
-
-# All languages that have a Hybrid Encryption CLI.
-LANGUAGES = ('cc', 'go', 'java', 'python')
-
-# Path are relative to tools directory.
-_ENCRYPT_CLI_PATHS = {
-    'cc': 'testing/cc/hybrid_encrypt_cli_cc',
-    'go': 'testing/go/hybrid_encrypt_cli_go',
-    'java': 'testing/hybrid_encrypt_cli_java',
-    'python': 'testing/python/hybrid_encrypt_cli_python',
-}
-
-_DECRYPT_CLI_PATHS = {
-    'cc': 'testing/cc/hybrid_decrypt_cli_cc',
-    'go': 'testing/go/hybrid_decrypt_cli_go',
-    'java': 'testing/hybrid_decrypt_cli_java',
-    'python': 'testing/python/hybrid_decrypt_cli_python',
-}
-
-
-def _tools_path() -> Text:
-  util_path = os.path.dirname(os.path.abspath(__file__))
-  return os.path.dirname(os.path.dirname(os.path.dirname(util_path)))
-
-
-class CliHybridEncrypt(hybrid.HybridEncrypt):
-  """Wraps a HybridEncrypt CLI binary into a Python primitive."""
-
-  def __init__(self, lang: Text,
-               public_keyset_handle: tink.KeysetHandle) -> None:
-    self.lang = lang
-    self._cli = os.path.join(_tools_path(), _ENCRYPT_CLI_PATHS[lang])
-    self._public_keyset_handle = public_keyset_handle
-
-  def encrypt(self, plaintext: bytes, context_info: bytes) -> bytes:
-    with tempfile.TemporaryDirectory() as tmpdir:
-      public_keyset_filename = os.path.join(tmpdir, 'public_keyset_file')
-      with open(public_keyset_filename, 'wb') as f:
-        cleartext_keyset_handle.write(
-            tink.BinaryKeysetWriter(f), self._public_keyset_handle)
-      plaintext_filename = os.path.join(tmpdir, 'plaintext_file')
-      with open(plaintext_filename, 'wb') as f:
-        f.write(plaintext)
-      context_info_filename = os.path.join(tmpdir, 'context_info_file')
-      with open(context_info_filename, 'wb') as f:
-        f.write(context_info)
-      ciphertext_filename = os.path.join(tmpdir, 'ciphertext_file')
-      try:
-        unused_return_value = subprocess.check_output([
-            self._cli, public_keyset_filename, plaintext_filename,
-            context_info_filename, ciphertext_filename
-        ])
-      except subprocess.CalledProcessError as e:
-        raise tink.TinkError(e)
-      with open(ciphertext_filename, 'rb') as f:
-        ciphertext = f.read()
-      return ciphertext
-
-
-class CliHybridDecrypt(hybrid.HybridDecrypt):
-  """Wraps a HybridDecrypt CLI binary into a Python primitive."""
-
-  def __init__(self, lang: Text,
-               private_keyset_handle: tink.KeysetHandle) -> None:
-    self._cli = os.path.join(_tools_path(), _DECRYPT_CLI_PATHS[lang])
-    self._private_keyset_handle = private_keyset_handle
-
-  def decrypt(self, ciphertext: bytes, context_info: bytes) -> bytes:
-    with tempfile.TemporaryDirectory() as tmpdir:
-      private_keyset_filename = os.path.join(tmpdir, 'private_keyset_file')
-      with open(private_keyset_filename, 'wb') as f:
-        cleartext_keyset_handle.write(
-            tink.BinaryKeysetWriter(f), self._private_keyset_handle)
-      ciphertext_filename = os.path.join(tmpdir, 'ciphertext_file')
-      with open(ciphertext_filename, 'wb') as f:
-        f.write(ciphertext)
-      context_info_filename = os.path.join(tmpdir, 'context_info_file')
-      with open(context_info_filename, 'wb') as f:
-        f.write(context_info)
-      decrypted_filename = os.path.join(tmpdir, 'decrypted_file')
-      try:
-        unused_return_value = subprocess.check_output([
-            self._cli, private_keyset_filename, ciphertext_filename,
-            context_info_filename, decrypted_filename
-        ])
-      except subprocess.CalledProcessError as e:
-        raise tink.TinkError(e)
-      with open(decrypted_filename, 'rb') as f:
-        plaintext = f.read()
-      return plaintext
diff --git a/tools/testing/cross_language/util/cli_hybrid_test.py b/tools/testing/cross_language/util/cli_hybrid_test.py
deleted file mode 100644
index 4d5ea5e..0000000
--- a/tools/testing/cross_language/util/cli_hybrid_test.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# 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 tink.tools.testing.cross_language.util.cli_hybrid."""
-
-from absl.testing import absltest
-from absl.testing import parameterized
-
-import tink
-from tink import hybrid
-
-from tools.testing.cross_language.util import cli_hybrid
-
-
-def setUpModule():
-  hybrid.register()
-
-
-class CliHybridTest(parameterized.TestCase):
-
-  @parameterized.parameters(*cli_hybrid.LANGUAGES)
-  def test_encrypt_decrypt_success(self, lang):
-    private_keyset_handle = tink.new_keyset_handle(
-        hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
-    public_keyset_handle = private_keyset_handle.public_keyset_handle()
-    enc = cli_hybrid.CliHybridEncrypt(lang, public_keyset_handle)
-    dec = cli_hybrid.CliHybridDecrypt(lang, private_keyset_handle)
-    plaintext = b'plaintext'
-    context_info = b'context_info'
-    ciphertext = enc.encrypt(plaintext, context_info)
-    output = dec.decrypt(ciphertext, context_info)
-    self.assertEqual(output, plaintext)
-
-  @parameterized.parameters(*cli_hybrid.LANGUAGES)
-  def test_invalid_decrypt_raises_error(self, lang):
-    private_keyset_handle = tink.new_keyset_handle(
-        hybrid.hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
-    dec = cli_hybrid.CliHybridDecrypt(lang, private_keyset_handle)
-    with self.assertRaises(tink.TinkError):
-      dec.decrypt(b'invalid ciphertext', b'context_info')
-
-
-if __name__ == '__main__':
-  absltest.main()
diff --git a/tools/testing/cross_language/util/cli_mac.py b/tools/testing/cross_language/util/cli_mac.py
deleted file mode 100644
index 4b485e3..0000000
--- a/tools/testing/cross_language/util/cli_mac.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# 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.
-"""Wraps a AEAD CLI into a Python Tink Aead class."""
-
-# Placeholder for import for type annotations
-
-import os
-import subprocess
-import tempfile
-
-import tink
-from tink import cleartext_keyset_handle
-from tink import mac
-
-from typing import Text
-
-# All languages that have an AEAD CLI.
-LANGUAGES = ('cc', 'go', 'java', 'python')
-
-# Path are relative to tools directory.
-_MAC_CLI_PATHS = {
-    'cc': 'testing/cc/mac_cli_cc',
-    'go': 'testing/go/mac_cli_go',
-    'java': 'testing/mac_cli_java',
-    'python': 'testing/python/mac_cli_python',
-}
-
-
-def _tools_path() -> Text:
-  util_path = os.path.dirname(os.path.abspath(__file__))
-  return os.path.dirname(os.path.dirname(os.path.dirname(util_path)))
-
-
-class CliMac(mac.Mac):
-  """Wraps a Mac CLI binary into a Python primitive."""
-
-  def __init__(self, lang: Text, keyset_handle: tink.KeysetHandle) -> None:
-    self.lang = lang
-    self._cli = os.path.join(_tools_path(), _MAC_CLI_PATHS[lang])
-    self._keyset_handle = keyset_handle
-
-  def compute_mac(self, data: bytes) -> bytes:
-    with tempfile.TemporaryDirectory() as tmpdir:
-      keyset_filename = os.path.join(tmpdir, 'keyset_file')
-      with open(keyset_filename, 'wb') as f:
-        cleartext_keyset_handle.write(
-            tink.BinaryKeysetWriter(f), self._keyset_handle)
-      data_filename = os.path.join(tmpdir, 'data_file')
-      with open(data_filename, 'wb') as f:
-        f.write(data)
-      mac_filename = os.path.join(tmpdir, 'mac_file')
-      try:
-        unused_return_value = subprocess.check_output([
-            self._cli, keyset_filename, 'compute', data_filename, mac_filename
-        ])
-      except subprocess.CalledProcessError as e:
-        raise tink.TinkError(e)
-      with open(mac_filename, 'rb') as f:
-        mac_value = f.read()
-      return mac_value
-
-  def verify_mac(self, mac_value: bytes, data: bytes) -> None:
-    with tempfile.TemporaryDirectory() as tmpdir:
-      keyset_filename = os.path.join(tmpdir, 'keyset_file')
-      with open(keyset_filename, 'wb') as f:
-        cleartext_keyset_handle.write(
-            tink.BinaryKeysetWriter(f), self._keyset_handle)
-      data_filename = os.path.join(tmpdir, 'data_file')
-      with open(data_filename, 'wb') as f:
-        f.write(data)
-      mac_filename = os.path.join(tmpdir, 'mac_file')
-      with open(mac_filename, 'wb') as f:
-        f.write(mac_value)
-      result_filename = os.path.join(tmpdir, 'result_file')
-      try:
-        unused_return_value = subprocess.check_output([
-            self._cli, keyset_filename, 'verify',
-            data_filename, mac_filename, result_filename
-        ])
-      except subprocess.CalledProcessError as e:
-        raise tink.TinkError(e)
-      with open(result_filename, 'rb') as f:
-        result = f.read()
-      if result != b'valid':
-        raise tink.TinkError('verification failed')
-      return None
diff --git a/tools/testing/cross_language/util/cli_mac_test.py b/tools/testing/cross_language/util/cli_mac_test.py
deleted file mode 100644
index eebcc77..0000000
--- a/tools/testing/cross_language/util/cli_mac_test.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# 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 tink.tools.testing.cross_language.util.cli_mac."""
-
-from absl.testing import absltest
-from absl.testing import parameterized
-
-import tink
-from tink import mac
-
-from tools.testing.cross_language.util import cli_mac
-
-
-def setUpModule():
-  mac.register()
-
-
-class MacCliWrapperTest(parameterized.TestCase):
-
-  @parameterized.parameters(*cli_mac.LANGUAGES)
-  def test_mac_success(self, lang):
-    keyset_handle = tink.new_keyset_handle(
-        mac.mac_key_templates.HMAC_SHA256_128BITTAG)
-    mac_primitive = cli_mac.CliMac(lang, keyset_handle)
-    data = b'data'
-    mac_value = mac_primitive.compute_mac(data)
-    self.assertIsNone(mac_primitive.verify_mac(mac_value, data))
-
-  @parameterized.parameters(*cli_mac.LANGUAGES)
-  def test_mac_wrong(self, lang):
-    keyset_handle = tink.new_keyset_handle(
-        mac.mac_key_templates.HMAC_SHA256_128BITTAG)
-    mac_primitive = cli_mac.CliMac(lang, keyset_handle)
-    with self.assertRaisesRegex(tink.TinkError, 'verification failed'):
-      mac_primitive.verify_mac(b'0123456789ABCDEF', b'data')
-
-
-if __name__ == '__main__':
-  absltest.main()
diff --git a/tools/testing/cross_language/util/cli_signature.py b/tools/testing/cross_language/util/cli_signature.py
deleted file mode 100644
index e3c2c99..0000000
--- a/tools/testing/cross_language/util/cli_signature.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# 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.
-"""Wraps Sign and Verify CLIs into a Python Tink signature classes."""
-
-# Placeholder for import for type annotations
-
-import os
-import subprocess
-import tempfile
-
-import tink
-from tink import cleartext_keyset_handle
-from tink import signature
-
-from typing import Text
-
-# All languages that have an AEAD CLI.
-LANGUAGES = ('cc', 'go', 'java', 'python')
-
-# Path are relative to tools directory.
-_SIGN_CLI_PATHS = {
-    'cc': 'testing/cc/public_key_sign_cli_cc',
-    'go': 'testing/go/public_key_sign_cli_go',
-    'java': 'testing/public_key_sign_cli_java',
-    'python': 'testing/python/public_key_sign_cli_python',
-}
-
-_VERIFY_CLI_PATHS = {
-    'cc': 'testing/cc/public_key_verify_cli_cc',
-    'go': 'testing/go/public_key_verify_cli_go',
-    'java': 'testing/public_key_verify_cli_java',
-    'python': 'testing/python/public_key_verify_cli_python',
-}
-
-
-def _tools_path() -> Text:
-  util_path = os.path.dirname(os.path.abspath(__file__))
-  return os.path.dirname(os.path.dirname(os.path.dirname(util_path)))
-
-
-class CliPublicKeySign(signature.PublicKeySign):
-  """Wraps a PublicKeySign CLI binary into a Python primitive."""
-
-  def __init__(self, lang: Text,
-               private_keyset_handle: tink.KeysetHandle) -> None:
-    self.lang = lang
-    self._cli = os.path.join(_tools_path(), _SIGN_CLI_PATHS[lang])
-    self._private_keyset_handle = private_keyset_handle
-
-  def sign(self, message: bytes) -> bytes:
-    with tempfile.TemporaryDirectory() as tmpdir:
-      private_keyset_filename = os.path.join(tmpdir, 'private_keyset_file')
-      with open(private_keyset_filename, 'wb') as f:
-        cleartext_keyset_handle.write(
-            tink.BinaryKeysetWriter(f), self._private_keyset_handle)
-      message_filename = os.path.join(tmpdir, 'message_filename')
-      with open(message_filename, 'wb') as f:
-        f.write(message)
-      output_filename = os.path.join(tmpdir, 'output_file')
-      try:
-        unused_return_value = subprocess.check_output([
-            self._cli, private_keyset_filename, message_filename,
-            output_filename
-        ])
-      except subprocess.CalledProcessError as e:
-        raise tink.TinkError(e)
-      with open(output_filename, 'rb') as f:
-        output = f.read()
-      return output
-
-
-class CliPublicKeyVerify(signature.PublicKeyVerify):
-  """Wraps a PublicKeyVerify CLI binary into a Python primitive."""
-
-  def __init__(self, lang: Text,
-               public_keyset_handle: tink.KeysetHandle) -> None:
-    self.lang = lang
-    self._cli = os.path.join(_tools_path(), _VERIFY_CLI_PATHS[lang])
-    self._public_keyset_handle = public_keyset_handle
-
-  def verify(self, sign: bytes, data: bytes) -> None:
-    with tempfile.TemporaryDirectory() as tmpdir:
-      public_keyset_filename = os.path.join(tmpdir, 'public_keyset_file')
-      with open(public_keyset_filename, 'wb') as f:
-        cleartext_keyset_handle.write(
-            tink.BinaryKeysetWriter(f), self._public_keyset_handle)
-      signature_filename = os.path.join(tmpdir, 'signature_file')
-      with open(signature_filename, 'wb') as f:
-        f.write(sign)
-      message_filename = os.path.join(tmpdir, 'message_file')
-      with open(message_filename, 'wb') as f:
-        f.write(data)
-      output_filename = os.path.join(tmpdir, 'output_file')
-      try:
-        unused_return_value = subprocess.check_output([
-            self._cli, public_keyset_filename, signature_filename,
-            message_filename, output_filename
-        ])
-      except subprocess.CalledProcessError as e:
-        raise tink.TinkError(e)
-      with open(output_filename, 'rb') as f:
-        output = f.read()
-      if output != b'valid':
-        raise tink.TinkError('verification failed')
-      return None
diff --git a/tools/testing/cross_language/util/cli_signature_test.py b/tools/testing/cross_language/util/cli_signature_test.py
deleted file mode 100644
index d6007bc..0000000
--- a/tools/testing/cross_language/util/cli_signature_test.py
+++ /dev/null
@@ -1,53 +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.
-"""Tests for tink.tools.testing.cross_language.util.cli_signature."""
-
-from absl.testing import absltest
-from absl.testing import parameterized
-
-import tink
-from tink import signature
-
-from tools.testing.cross_language.util import cli_signature
-
-
-def setUpModule():
-  signature.register()
-
-
-class CliSignatureTest(parameterized.TestCase):
-
-  @parameterized.parameters(*cli_signature.LANGUAGES)
-  def test_sign_verify_success(self, lang):
-    private_keyset_handle = tink.new_keyset_handle(
-        signature.signature_key_templates.ECDSA_P256)
-    public_keyset_handle = private_keyset_handle.public_keyset_handle()
-    signer = cli_signature.CliPublicKeySign(lang, private_keyset_handle)
-    verifier = cli_signature.CliPublicKeyVerify(lang, public_keyset_handle)
-    message = b'message'
-    sign = signer.sign(message)
-    self.assertIsNone(verifier.verify(sign, message))
-
-  @parameterized.parameters(*cli_signature.LANGUAGES)
-  def test_invalid_decrypt_raises_error(self, lang):
-    private_keyset_handle = tink.new_keyset_handle(
-        signature.signature_key_templates.ECDSA_P256)
-    public_keyset_handle = private_keyset_handle.public_keyset_handle()
-    verifier = cli_signature.CliPublicKeyVerify(lang, public_keyset_handle)
-    with self.assertRaises(tink.TinkError):
-      verifier.verify(b'invalid signature', b'message')
-
-
-if __name__ == '__main__':
-  absltest.main()
diff --git a/tools/testing/cross_language/util/cli_tinkey.py b/tools/testing/cross_language/util/cli_tinkey.py
deleted file mode 100644
index 6bdff99..0000000
--- a/tools/testing/cross_language/util/cli_tinkey.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# 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.
-"""Python wrapper for Tinkey CLI."""
-
-# Placeholder for import for type annotations
-
-import os
-import subprocess
-import tempfile
-
-import tink
-from tink import cleartext_keyset_handle
-
-AEAD_KEY_TEMPLATES = ('AES128_GCM', 'AES256_GCM', 'AES128_CTR_HMAC_SHA256',
-                      'AES256_CTR_HMAC_SHA256', 'XCHACHA20_POLY1305',
-                      'AES128_EAX', 'AES256_EAX', 'CHACHA20_POLY1305')
-
-DAEAD_KEY_TEMPLATE = 'AES256_SIV'
-
-HYBRID_KEY_TEMPLATES = (
-    'ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256',
-    'ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM')
-
-MAC_KEY_TEMPLATES = ('HMAC_SHA256_128BITTAG', 'HMAC_SHA256_256BITTAG',
-                     'HMAC_SHA512_256BITTAG', 'HMAC_SHA512_512BITTAG')
-
-# Path is relative to tools directory
-_TINKEY_CLI_PATH = 'tinkey/tinkey'
-
-
-def _tools_path():
-  util_path = os.path.dirname(os.path.abspath(__file__))
-  return os.path.dirname(os.path.dirname(os.path.dirname(util_path)))
-
-
-def generate_keyset_handle(key_template) -> tink.KeysetHandle:
-  """Generates a keyset handle from a key templates."""
-  with tempfile.TemporaryDirectory() as tmpdir:
-    keyset_filename = os.path.join(tmpdir, 'keyset_file')
-    cli_path = os.path.join(_tools_path(), _TINKEY_CLI_PATH)
-    unused_return_value = subprocess.check_output([
-        cli_path, 'create-keyset',
-        '--key-template', key_template,
-        '--out-format', 'BINARY',
-        '--out', keyset_filename
-    ])
-    with open(keyset_filename, 'rb') as f:
-      keyset_data = f.read()
-    return cleartext_keyset_handle.read(tink.BinaryKeysetReader(keyset_data))
-
-
-def public_keyset_handle(private_keyset_handle) -> tink.KeysetHandle:
-  """Generates a public keyset handle from a private one."""
-  with tempfile.TemporaryDirectory() as tmpdir:
-    cli_path = os.path.join(_tools_path(), _TINKEY_CLI_PATH)
-    private_keyset_filename = os.path.join(tmpdir, 'private_keyset_file')
-    with open(private_keyset_filename, 'wb') as f:
-      cleartext_keyset_handle.write(
-          tink.BinaryKeysetWriter(f), private_keyset_handle)
-    public_keyset_filename = os.path.join(tmpdir, 'public_keyset_file')
-    unused_return_value = subprocess.check_output([
-        cli_path, 'create-public-keyset',
-        '--in-format', 'BINARY',
-        '--in', private_keyset_filename,
-        '--out-format', 'BINARY',
-        '--out', public_keyset_filename,
-    ])
-    with open(public_keyset_filename, 'rb') as f:
-      public_keyset_data = f.read()
-    return cleartext_keyset_handle.read(
-        tink.BinaryKeysetReader(public_keyset_data))
diff --git a/tools/testing/cross_language/util/cli_tinkey_test.py b/tools/testing/cross_language/util/cli_tinkey_test.py
deleted file mode 100644
index 04ea6ed..0000000
--- a/tools/testing/cross_language/util/cli_tinkey_test.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# 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 tink.tools.testing.cross_language.util.tinkey_cli."""
-
-from absl.testing import absltest
-from absl.testing import parameterized
-from tools.testing.cross_language.util import cli_aead
-from tools.testing.cross_language.util import cli_daead
-from tools.testing.cross_language.util import cli_hybrid
-from tools.testing.cross_language.util import cli_mac
-from tools.testing.cross_language.util import cli_tinkey
-
-
-class TinkeyCliWrapperTest(parameterized.TestCase):
-
-  @parameterized.parameters(*cli_tinkey.AEAD_KEY_TEMPLATES)
-  def test_generate_encrypt_decrypt(self, key_template):
-    keyset_handle = cli_tinkey.generate_keyset_handle(key_template)
-    primitive = cli_aead.CliAead('java', keyset_handle)
-    plaintext = b'plaintext'
-    associated_data = b'associated_data'
-    ciphertext = primitive.encrypt(plaintext, associated_data)
-    output = primitive.decrypt(ciphertext, associated_data)
-    self.assertEqual(output, plaintext)
-
-  def test_generate_encrypt_decrypt_deterministically(self):
-    keyset_handle = cli_tinkey.generate_keyset_handle(
-        cli_tinkey.DAEAD_KEY_TEMPLATE)
-    p = cli_daead.CliDeterministicAead('java', keyset_handle)
-    plaintext = b'plaintext'
-    associated_data = b'associated_data'
-    ciphertext = p.encrypt_deterministically(plaintext, associated_data)
-    output = p.decrypt_deterministically(ciphertext, associated_data)
-    self.assertEqual(output, plaintext)
-
-  @parameterized.parameters(*cli_tinkey.MAC_KEY_TEMPLATES)
-  def test_mac_generate_compute_verify(self, key_template):
-    keyset_handle = cli_tinkey.generate_keyset_handle(key_template)
-    p = cli_mac.CliMac('java', keyset_handle)
-    data = b'data'
-    mac_value = p.compute_mac(data)
-    self.assertIsNone(p.verify_mac(mac_value, data))
-
-  @parameterized.parameters(*cli_tinkey.HYBRID_KEY_TEMPLATES)
-  def test_hybrid_generate_encrypt_decrypt(self, key_template):
-    private_handle = cli_tinkey.generate_keyset_handle(key_template)
-    public_handle = cli_tinkey.public_keyset_handle(private_handle)
-    enc = cli_hybrid.CliHybridEncrypt('java', public_handle)
-    dec = cli_hybrid.CliHybridDecrypt('java', private_handle)
-    plaintext = b'plaintext'
-    context_info = b'context_info'
-    ciphertext = enc.encrypt(plaintext, context_info)
-    output = dec.decrypt(ciphertext, context_info)
-    self.assertEqual(output, plaintext)
-
-if __name__ == '__main__':
-  absltest.main()
diff --git a/tools/testing/cross_language/util/keyset_manager.py b/tools/testing/cross_language/util/keyset_manager.py
deleted file mode 100644
index 3c31a89..0000000
--- a/tools/testing/cross_language/util/keyset_manager.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# 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.
-"""Python implementation of a KeysetManager."""
-
-# Placeholder for import for type annotations
-
-import tink
-from tink.proto import tink_pb2
-from tools.testing.cross_language.util import cli_tinkey
-
-
-_CHACHA20_POLY1305_KEY_TYPES = (
-    'type.googleapis.com/google.crypto.tink.ChaCha20Poly1305Key')
-
-
-def new_keyset_handle(key_template: tink_pb2.KeyTemplate) -> tink.KeysetHandle:
-  if key_template.type_url == _CHACHA20_POLY1305_KEY_TYPES:
-    return cli_tinkey.generate_keyset_handle('CHACHA20_POLY1305')
-  return tink.new_keyset_handle(key_template)
diff --git a/tools/testing/javatests/com/google/crypto/tink/testing/BUILD.bazel b/tools/testing/javatests/com/google/crypto/tink/testing/BUILD.bazel
index 31f301a..33adfbc 100644
--- a/tools/testing/javatests/com/google/crypto/tink/testing/BUILD.bazel
+++ b/tools/testing/javatests/com/google/crypto/tink/testing/BUILD.bazel
@@ -7,10 +7,10 @@
     size = "small",
     srcs = ["CompareKeysetsTest.java"],
     deps = [
-        "@tink_java//src/main/java/com/google/crypto/tink/aead",
-        "@tink_java//src/main/java/com/google/crypto/tink/testing:test_util",
-        "@tink_java//proto:tink_java_proto",
         "//testing:compare_keysets",
         "@maven//:junit_junit",
+        "@tink_java//proto:tink_java_proto",
+        "@tink_java//src/main/java/com/google/crypto/tink/aead:aes_gcm_key_manager",
+        "@tink_java//src/main/java/com/google/crypto/tink/testing:test_util",
     ],
 )
diff --git a/tools/testing/python/deterministic_aead_cli.py b/tools/testing/python/deterministic_aead_cli.py
index a1c5e8a..e179402 100644
--- a/tools/testing/python/deterministic_aead_cli.py
+++ b/tools/testing/python/deterministic_aead_cli.py
@@ -54,7 +54,8 @@
   """
   with open(keyset_filename, 'rb') as keyset_file:
     text = keyset_file.read()
-    keyset = cleartext_keyset_handle.read(tink.BinaryKeysetReader(text))
+    keyset = cleartext_keyset_handle.read(
+        tink.BinaryKeysetReader(text))
   return keyset
 
 
diff --git a/tools/tinkey/BUILD.bazel b/tools/tinkey/BUILD.bazel
index a25242d..7b3fbd7 100644
--- a/tools/tinkey/BUILD.bazel
+++ b/tools/tinkey/BUILD.bazel
@@ -1,9 +1,10 @@
+load("@tink_java//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
+load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
+
 package(default_visibility = ["//:__subpackages__"])
 
 licenses(["notice"])
 
-load("@tink_java//tools/build_defs:javac.bzl", "JAVACOPTS_OSS")
-
 java_library(
     name = "libtinkey",
     srcs = glob(
@@ -15,23 +16,37 @@
         ],
     ),
     javacopts = JAVACOPTS_OSS,
+    runtime_deps = [
+        # Tinkey automatically loads these KMS clients at runtime.
+        "@tink_java//src/main/java/com/google/crypto/tink/integration/awskms:aws_kms_client",
+        "@tink_java//src/main/java/com/google/crypto/tink/integration/gcpkms:gcp_kms_client",
+    ],
     deps = [
+        "@com_google_protobuf//:protobuf_javalite",
+        "@maven//:args4j_args4j",
         "@tink_java//:awskms",
         "@tink_java//:cleartext_keyset_handle",
         "@tink_java//:gcpkms",
         "@tink_java//:subtle",
-        "@tink_java//src/main/java/com/google/crypto/tink",
-        "@tink_java//src/main/java/com/google/crypto/tink:primitives",
-        "@tink_java//src/main/java/com/google/crypto/tink/aead",
-        "@tink_java//src/main/java/com/google/crypto/tink/daead",
-        "@tink_java//src/main/java/com/google/crypto/tink/hybrid",
-        "@tink_java//src/main/java/com/google/crypto/tink/mac",
-        "@tink_java//src/main/java/com/google/crypto/tink/prf:prf_key_templates",
-        "@tink_java//src/main/java/com/google/crypto/tink/signature",
-        "@tink_java//src/main/java/com/google/crypto/tink/streamingaead",
         "@tink_java//proto:tink_java_proto",
-        "@com_google_protobuf//:protobuf_javalite",
-        "@maven//:args4j_args4j",
+        "@tink_java//src/main/java/com/google/crypto/tink:aead",
+        "@tink_java//src/main/java/com/google/crypto/tink:binary_keyset_reader",
+        "@tink_java//src/main/java/com/google/crypto/tink:binary_keyset_writer",
+        "@tink_java//src/main/java/com/google/crypto/tink:cleartext_keyset_handle",
+        "@tink_java//src/main/java/com/google/crypto/tink:json_keyset_reader",
+        "@tink_java//src/main/java/com/google/crypto/tink:json_keyset_writer",
+        "@tink_java//src/main/java/com/google/crypto/tink:keyset_reader",
+        "@tink_java//src/main/java/com/google/crypto/tink:keyset_writer",
+        "@tink_java//src/main/java/com/google/crypto/tink:kms_clients",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
+        "@tink_java//src/main/java/com/google/crypto/tink/aead:aead_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/daead:deterministic_aead_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/hybrid:hybrid_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/mac:mac_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/prf:prf_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/signature:signature_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/streamingaead:streaming_aead_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/subtle:validators",
     ],
 )
 
@@ -45,15 +60,13 @@
     visibility = ["//testing:__subpackages__"],
     deps = [
         ":libtinkey",
-        "@tink_java//src/main/java/com/google/crypto/tink/config",
         "@maven//:args4j_args4j",
+        "@tink_java//src/main/java/com/google/crypto/tink/config:tink_config",
     ],
 )
 
 # tests
 
-load("@tink_java//tools:gen_java_test_rules.bzl", "gen_java_test_rules")
-
 java_library(
     name = "generator_test",
     testonly = 1,
@@ -62,11 +75,24 @@
     ]),
     deps = [
         ":libtinkey",
-        "@tink_java//:testonly",
-        "@tink_java//:testutil",
         "@com_google_protobuf//:protobuf_javalite",
         "@maven//:com_google_truth_truth",
         "@maven//:junit_junit",
+        "@tink_java//proto:tink_java_proto",
+        "@tink_java//src/main/java/com/google/crypto/tink:cleartext_keyset_handle",
+        "@tink_java//src/main/java/com/google/crypto/tink:config",
+        "@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:keyset_reader",
+        "@tink_java//src/main/java/com/google/crypto/tink:public_key_sign",
+        "@tink_java//src/main/java/com/google/crypto/tink:public_key_verify",
+        "@tink_java//src/main/java/com/google/crypto/tink:registry_cluster",
+        "@tink_java//src/main/java/com/google/crypto/tink/config:tink_config",
+        "@tink_java//src/main/java/com/google/crypto/tink/hybrid:hybrid_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/mac:mac_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/signature:signature_key_templates",
+        "@tink_java//src/main/java/com/google/crypto/tink/subtle:random",
+        "@tink_java//src/main/java/com/google/crypto/tink/testing:test_util",
     ],
 )
 
diff --git a/tools/tinkey/src/main/java/com/google/crypto/tink/tinkey/ListKeyTemplatesCommand.java b/tools/tinkey/src/main/java/com/google/crypto/tink/tinkey/ListKeyTemplatesCommand.java
index 1f64b6e..0deb7ab 100644
--- a/tools/tinkey/src/main/java/com/google/crypto/tink/tinkey/ListKeyTemplatesCommand.java
+++ b/tools/tinkey/src/main/java/com/google/crypto/tink/tinkey/ListKeyTemplatesCommand.java
@@ -16,9 +16,7 @@
 
 package com.google.crypto.tink.tinkey;
 
-import com.google.crypto.tink.proto.KeyTemplate;
-
-/** Creates a new {@link KeyTemplate}. */
+/** Creates a new {@link com.google.crypto.tink.proto.KeyTemplate}. */
 public class ListKeyTemplatesCommand implements Command {
 
   @Override
diff --git a/tools/tinkey/src/test/java/com/google/crypto/tink/tinkey/CreatePublicKeysetCommandTest.java b/tools/tinkey/src/test/java/com/google/crypto/tink/tinkey/CreatePublicKeysetCommandTest.java
index 77646ee..8eea3ba 100644
--- a/tools/tinkey/src/test/java/com/google/crypto/tink/tinkey/CreatePublicKeysetCommandTest.java
+++ b/tools/tinkey/src/test/java/com/google/crypto/tink/tinkey/CreatePublicKeysetCommandTest.java
@@ -28,14 +28,10 @@
 import com.google.crypto.tink.PublicKeySign;
 import com.google.crypto.tink.PublicKeyVerify;
 import com.google.crypto.tink.config.TinkConfig;
-import com.google.crypto.tink.hybrid.HybridDecryptFactory;
-import com.google.crypto.tink.hybrid.HybridEncryptFactory;
 import com.google.crypto.tink.hybrid.HybridKeyTemplates;
 import com.google.crypto.tink.proto.EncryptedKeyset;
 import com.google.crypto.tink.proto.KeyTemplate;
 import com.google.crypto.tink.proto.Keyset;
-import com.google.crypto.tink.signature.PublicKeySignFactory;
-import com.google.crypto.tink.signature.PublicKeyVerifyFactory;
 import com.google.crypto.tink.signature.SignatureKeyTemplates;
 import com.google.crypto.tink.subtle.Random;
 import com.google.crypto.tink.testing.TestUtil;
@@ -161,10 +157,10 @@
 
   private void assertHybrid(KeysetReader privateReader, KeysetReader publicReader)
     throws Exception {
-    HybridDecrypt decrypter = HybridDecryptFactory.getPrimitive(
-        CleartextKeysetHandle.read(privateReader));
-    HybridEncrypt encrypter = HybridEncryptFactory.getPrimitive(
-        CleartextKeysetHandle.read(publicReader));
+    KeysetHandle privateHandle = CleartextKeysetHandle.read(privateReader);
+    HybridDecrypt decrypter = privateHandle.getPrimitive(HybridDecrypt.class);
+    KeysetHandle publicHandle = CleartextKeysetHandle.read(publicReader);
+    HybridEncrypt encrypter = publicHandle.getPrimitive(HybridEncrypt.class);
     byte[] message = Random.randBytes(10);
     byte[] contextInfo = Random.randBytes(20);
 
@@ -175,10 +171,10 @@
   private void assertSignature(KeysetReader privateReader, KeysetReader publicReader)
     throws Exception {
     byte[] message = Random.randBytes(10);
-    PublicKeySign signer = PublicKeySignFactory.getPrimitive(
-        CleartextKeysetHandle.read(privateReader));
-    PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive(
-        CleartextKeysetHandle.read(publicReader));
+    KeysetHandle privateHandle = CleartextKeysetHandle.read(privateReader);
+    PublicKeySign signer = privateHandle.getPrimitive(PublicKeySign.class);
+    KeysetHandle publicHandle = CleartextKeysetHandle.read(publicReader);
+    PublicKeyVerify verifier = publicHandle.getPrimitive(PublicKeyVerify.class);
 
     verifier.verify(signer.sign(message), message);
   }
@@ -192,8 +188,6 @@
         case SIGNATURE:
             assertSignature(privateReader, publicReader);
             break;
-        default:
-            throw new Exception("not supported: " + type);
     }
   }
 }