diff --git a/__init__.py b/__init__.py
index 872613ccf717aed7cde5a5bca169733d18bc6900..5b8c5ab8aeb3c9a19ea95d27cd96f4c79a75e896 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,16 +1,30 @@
+# 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.
+
 """Tink package."""
 from __future__ import absolute_import
 from __future__ import division
 from __future__ import google_type_annotations
 from __future__ import print_function
 
-from google3.third_party.tink.python import aead
-from google3.third_party.tink.python import core
-from google3.third_party.tink.python import daead
-from google3.third_party.tink.python import hybrid
-from google3.third_party.tink.python import mac
-from google3.third_party.tink.python import signature
-from google3.third_party.tink.python.core import tink_config
+from tink.python import aead
+from tink.python import core
+from tink.python import daead
+from tink.python import hybrid
+from tink.python import mac
+from tink.python import signature
+from tink.python.core import tink_config
 
 
 Aead = aead.Aead
diff --git a/cc/python/aead.clif b/cc/python/aead.clif
new file mode 100644
index 0000000000000000000000000000000000000000..ca1e653f14c3527b10edcde12a72b46f17d721a8
--- /dev/null
+++ b/cc/python/aead.clif
@@ -0,0 +1,37 @@
+# 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.
+
+from "tink/python/util/clif.h" import *  # StatusOr
+
+from "tink/cc/aead.h":
+  namespace `crypto::tink`:
+    # The interface for authenticated encryption with associated data.
+    # Implementations of this interface are secure against adaptive
+    # chosen ciphertext attacks.  Encryption with associated data ensures
+    # authenticity and integrity of that data, but not its secrecy.
+    # (see RFC 5116, https://tools.ietf.org/html/rfc5116)
+    class Aead:
+      # Encrypts 'plaintext' with 'associated_data' as associated data,
+      # and returns the resulting ciphertext.
+      # The ciphertext allows for checking authenticity and integrity
+      # of the associated data , but does not guarantee its secrecy.
+      def `Encrypt` as encrypt(self, plaintext: bytes, associated_data: bytes)
+        -> StatusOr<bytes>
+      # Decrypts 'ciphertext' with 'associated_data' as associated data,
+      # and returns the resulting plaintext.
+      # The decryption verifies the authenticity and integrity
+      # of the associated data, but there are no guarantees wrt. secrecy
+      # of that data.
+      def `Decrypt` as decrypt(self, ciphertext: bytes, associated_data: bytes)
+        -> StatusOr<bytes>
diff --git a/cc/python/deterministic_aead.clif b/cc/python/deterministic_aead.clif
new file mode 100644
index 0000000000000000000000000000000000000000..e66b4cf4b08a42a43efbcd3d37338dab2f9bad2b
--- /dev/null
+++ b/cc/python/deterministic_aead.clif
@@ -0,0 +1,45 @@
+# 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.
+
+from "tink/python/util/clif.h" import *  # StatusOr
+
+from "tink/cc/deterministic_aead.h":
+  namespace `crypto::tink`:
+    # Interface for Deterministic Authenticated Encryption with Associated Data
+    # (Deterministic AEAD)
+    #
+    # For why this interface is desirable and some of its use cases, see for
+    # example https://tools.ietf.org/html/rfc5297#section-1.3.
+    #
+    # Warning!
+    # Unlike Aead, implementations of this interface are not semantically
+    # secure, because encrypting the same plaintex always yields the same
+    # ciphertext.
+    #
+    # Security guarantees
+    #
+    # Implementations of this interface provide 128-bit security level against
+    # multi-user attacks with up to 2^32 keys. That means if an adversary
+    # obtains 2^32 ciphertexts of the same message encrypted under 2^32 keys,
+    # they need to do 2^128 computations to obtain a single key.
+    #
+    # Encryption with associated data ensures authenticity (who the sender is)
+    # and integrity (the data has not been tampered with) of that data, but not
+    # its secrecy. (see https://tools.ietf.org/html/rfc5116)
+    class DeterministicAead:
+
+      def `EncryptDeterministically` as encrypt_deterministically(
+          self, plaintext: bytes, associated_data: bytes) -> StatusOr<bytes>
+      def `DecryptDeterministically` as decrypt_deterministically(
+          self, ciphertext: bytes, associated_data: bytes) -> StatusOr<bytes>
diff --git a/cc/python/hybrid_decrypt.clif b/cc/python/hybrid_decrypt.clif
new file mode 100644
index 0000000000000000000000000000000000000000..a69f8fa4f1892d83f21678f04a368c70d6c5acd7
--- /dev/null
+++ b/cc/python/hybrid_decrypt.clif
@@ -0,0 +1,21 @@
+# 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.
+
+from "tink/python/util/clif.h" import *  # StatusOr
+
+from "tink/cc/hybrid_decrypt.h":
+  namespace `crypto::tink`:
+    class HybridDecrypt:
+      def `Decrypt` as decrypt(self, ciphertext: bytes, context_info: bytes)
+        -> StatusOr<bytes>
diff --git a/cc/python/hybrid_encrypt.clif b/cc/python/hybrid_encrypt.clif
new file mode 100644
index 0000000000000000000000000000000000000000..afad001b0abc9a6bf8708f353a7de9f796371d48
--- /dev/null
+++ b/cc/python/hybrid_encrypt.clif
@@ -0,0 +1,21 @@
+# 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.
+
+from "tink/python/util/clif.h" import *  # StatusOr
+
+from "tink/cc/hybrid_encrypt.h":
+  namespace `crypto::tink`:
+    class HybridEncrypt:
+      def `Encrypt` as encrypt(self, plaintext: bytes, context_info: bytes)
+        -> StatusOr<bytes>
diff --git a/cc/python/mac.clif b/cc/python/mac.clif
new file mode 100644
index 0000000000000000000000000000000000000000..3105b3e63eddad997af4f15ccba431e56e3fec6c
--- /dev/null
+++ b/cc/python/mac.clif
@@ -0,0 +1,30 @@
+# 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.
+
+from "tink/python/util/clif.h" import *  # Status, StatusOr
+from clif.python.postproc import DropOkStatus
+
+from "tink/cc/mac.h":
+  namespace `crypto::tink`:
+    # Interface for MACs (Message Authentication Codes).
+    # This interface should be used for authentication only, and not for other
+    # purposes (e.g., it should not be used to generate pseudorandom bytes).
+    class Mac:
+      # Computes and returns the message authentication code (MAC) for 'data'.
+      def `ComputeMac` as compute_mac(self, data: bytes) -> StatusOr<bytes>
+      # Verifies if 'mac' is a correct authentication code (MAC) for 'data'.
+      # Raises a StatusNotOk exception if the verification fails.
+      def `VerifyMac` as verify_mac(self, mac: bytes, data: bytes)
+          -> (ok: Status):
+        return DropOkStatus(...)
diff --git a/cc/python/public_key_sign.clif b/cc/python/public_key_sign.clif
new file mode 100644
index 0000000000000000000000000000000000000000..d599bd4abf3888c35fa6de2207fccdac635b03a8
--- /dev/null
+++ b/cc/python/public_key_sign.clif
@@ -0,0 +1,28 @@
+# 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.
+
+from "tink/python/util/clif.h" import *  # StatusOr
+
+from "tink/cc/public_key_sign.h":
+  namespace `crypto::tink`:
+    # Interface for public key signing.
+    # Digital Signatures provide functionality of signing data and verification
+    # of the signatures. They are represented by a pair of primitives
+    # (interfaces) 'PublicKeySign' for signing of data, and 'PublicKeyVerify'
+    # for verification of signatures. Implementations of these interfaces are
+    # secure against adaptive chosen-message attacks. Signing data ensures the
+    # authenticity and the integrity of that data, but not its secrecy.
+    class PublicKeySign:
+      # Computes the signature for 'data'.
+      def `Sign` as sign(self, data: bytes) -> StatusOr<bytes>
diff --git a/cc/python/public_key_verify.clif b/cc/python/public_key_verify.clif
new file mode 100644
index 0000000000000000000000000000000000000000..9ea3bb4bb0c6818b6413ea4a379900553d7506d8
--- /dev/null
+++ b/cc/python/public_key_verify.clif
@@ -0,0 +1,31 @@
+# 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.
+
+from "tink/python/util/clif.h" import *  # Status
+from clif.python.postproc import DropOkStatus
+
+from "tink/cc/public_key_verify.h":
+  namespace `crypto::tink`:
+    # Interface for public key verifying.
+    # Digital Signatures provide functionality of signing data and verification
+    # of the signatures. They are represented by a pair of primitives
+    # (interfaces) 'PublicKeySign' for signing of data, and 'PublicKeyVerify'
+    # for verification of signatures. Implementations of these interfaces are
+    # secure against adaptive chosen-message attacks. Signing data ensures the
+    # authenticity and the integrity of that data, but not its secrecy.
+    class PublicKeyVerify:
+      # Verifies that signature is a digital signature for data.
+      def `Verify` as verify(self, signature: bytes, data: bytes)
+        -> (ok: Status):
+        return DropOkStatus(...)
diff --git a/python/cc/cc_key_manager.h b/python/cc/cc_key_manager.h
new file mode 100644
index 0000000000000000000000000000000000000000..a1ceb54dd22544b9b49b0fe9dfd0b20c59a87d8a
--- /dev/null
+++ b/python/cc/cc_key_manager.h
@@ -0,0 +1,102 @@
+// Copyright 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_PYTHON_CC_CC_KEY_MANAGER_H_
+#define TINK_PYTHON_CC_CC_KEY_MANAGER_H_
+
+#include <algorithm>
+#include <vector>
+
+#include "tink/key_manager.h"
+#include "tink/util/errors.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+#include "proto/tink.pb.h"
+#include "tink/registry.h"
+
+namespace crypto {
+namespace tink {
+
+/**
+ * CcKeyManager is a thin wrapper of KeyManager in
+ * third_party/tink/cc/key_manager.h
+ * It only implements the methods currently needed in Python, and slightly
+ * changes the interface to ease usage of CLIF.
+ */
+template<class P>
+class CcKeyManager {
+ public:
+  // Returns a key manager from the registry.
+  static util::StatusOr<std::unique_ptr<CcKeyManager<P>>>
+  GetFromCcRegistry(const std::string& type_url) {
+    auto key_manager_result = Registry::get_key_manager<P>(type_url);
+    if (!key_manager_result.ok()) {
+      return ToStatusF(util::error::FAILED_PRECONDITION,
+                       "No manager for key type '%s' found in the registry.",
+                       type_url.c_str());
+    }
+    return absl::make_unique<CcKeyManager<P>>(
+        key_manager_result.ValueOrDie());
+  }
+
+  explicit CcKeyManager(const KeyManager<P>* key_manager)
+      : key_manager_(key_manager) {}
+
+  // Constructs an instance of P for the given 'key_data'.
+  crypto::tink::util::StatusOr<std::unique_ptr<P>> GetPrimitive(
+      const google::crypto::tink::KeyData& key_data) {
+    return key_manager_->GetPrimitive(key_data);
+  }
+
+  // Creates a new random key, based on the specified 'key_format'.
+  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+      NewKeyData(const google::crypto::tink::KeyTemplate& key_template) {
+    if (key_manager_->get_key_type() != key_template.type_url()) {
+      return ToStatusF(util::error::INVALID_ARGUMENT,
+                       "Key type '%s' is not supported by this manager.",
+                       key_template.type_url().c_str());
+    }
+    return key_manager_->get_key_factory().NewKeyData(key_template.value());
+  }
+
+  // Returns public key data extracted from the given private_key_data.
+  crypto::tink::util::StatusOr<std::unique_ptr<google::crypto::tink::KeyData>>
+  GetPublicKeyData(
+      const google::crypto::tink::KeyData& private_key_data) const {
+    const PrivateKeyFactory* factory = dynamic_cast<const PrivateKeyFactory*>(
+        &key_manager_->get_key_factory());
+    if (factory == nullptr) {
+      return ToStatusF(util::error::INVALID_ARGUMENT,
+                       "KeyManager for type '%s' does not have "
+                       "a PrivateKeyFactory.",
+                       key_manager_->get_key_type().c_str());
+    }
+    auto result = factory->GetPublicKeyData(private_key_data.value());
+    return result;
+  }
+
+  // Returns the type_url identifying the key type handled by this manager.
+  std::string KeyType() {
+    return key_manager_->get_key_type();
+  }
+
+ private:
+  const KeyManager<P>* key_manager_;
+};
+
+}  // namespace tink
+}  // namespace crypto
+#endif  // TINK_PYTHON_CC_CC_KEY_MANAGER_H_
diff --git a/python/cc/cc_tink_config.cc b/python/cc/cc_tink_config.cc
new file mode 100644
index 0000000000000000000000000000000000000000..53192eefc261bc7244d32422f039f409a766f8ad
--- /dev/null
+++ b/python/cc/cc_tink_config.cc
@@ -0,0 +1,46 @@
+// Copyright 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT 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/python/cc/cc_tink_config.h"
+
+#include "tink/config/tink_config.h"
+
+namespace crypto {
+namespace tink {
+
+util::Status CcTinkConfigRegister() {
+  return TinkConfig::Register();
+}
+
+const google::crypto::tink::RegistryConfig& CcTinkConfigLatest() {
+  return TinkConfig::Latest();
+}
+
+}  // namespace tink
+}  // namespace crypto
diff --git a/python/cc/cc_tink_config.h b/python/cc/cc_tink_config.h
new file mode 100644
index 0000000000000000000000000000000000000000..16eeb22acc7a13aec188cac4d4ec42ed278732b1
--- /dev/null
+++ b/python/cc/cc_tink_config.h
@@ -0,0 +1,34 @@
+// Copyright 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_PYTHON_CC_CC_TINK_CONFIG_H_
+#define TINK_PYTHON_CC_CC_TINK_CONFIG_H_
+
+#include "tink/util/status.h"
+#include "tink/registry.h"
+#include "proto/config.pb.h"
+
+namespace crypto {
+namespace tink {
+
+crypto::tink::util::Status CcTinkConfigRegister();
+
+const google::crypto::tink::RegistryConfig& CcTinkConfigLatest();
+
+}  // namespace tink
+}  // namespace crypto
+
+#endif  // TINK_PYTHON_CC_CC_TINK_CONFIG_H_
diff --git a/python/cc/clif/cc_key_manager.clif b/python/cc/clif/cc_key_manager.clif
new file mode 100644
index 0000000000000000000000000000000000000000..01fae038a92f89075a07228f447d72ab2e2abd7d
--- /dev/null
+++ b/python/cc/clif/cc_key_manager.clif
@@ -0,0 +1,119 @@
+# 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.
+
+from "tink/proto/tink_pyclif.h" import *  # KeyTemplate, KeyData
+from "tink/python/util/clif.h" import *  # StatusOr
+from "tink/cc/python/aead.h" import *  # Aead
+from "tink/cc/python/deterministic_aead.h" import *  # DeterministicAead
+from "tink/cc/python/hybrid_decrypt.h" import *  # HybridDecrypt
+from "tink/cc/python/hybrid_encrypt.h" import *  # HybridEncrypt
+from "tink/cc/python/mac.h" import *  # Mac
+from "tink/cc/python/public_key_sign.h" import *  # PublicKeySign
+from "tink/cc/python/public_key_verify.h" import *  # PublicKeyVerify
+
+from tink.cc.python.aead import Aead
+from tink.cc.python.deterministic_aead import DeterministicAead
+from tink.cc.python.hybrid_decrypt import HybridDecrypt
+from tink.cc.python.hybrid_encrypt import HybridEncrypt
+from tink.cc.python.mac import Mac
+
+from "tink/python/cc/cc_key_manager.h":
+  namespace `crypto::tink`:
+    # Key Manager for AEAD (authenticated encryption with associated data).
+    class `CcKeyManager<Aead>` as AeadKeyManager:
+      @classmethod
+      def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str)
+      -> StatusOr<AeadKeyManager>
+
+      def `GetPrimitive` as primitive(self, key_data:KeyData)
+        -> StatusOr<Aead>
+      def `KeyType` as key_type(self) -> str
+      def `NewKeyData` as new_key_data(self, key_template:KeyTemplate)
+        -> StatusOr<KeyData>
+
+    # Key Manager for Deterministic AEAD.
+    class `CcKeyManager<DeterministicAead>` as DeterministicAeadKeyManager:
+      @classmethod
+      def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str)
+      -> StatusOr<DeterministicAeadKeyManager>
+
+      def `GetPrimitive` as primitive(self, key_data:KeyData)
+        -> StatusOr<DeterministicAead>
+      def `KeyType` as key_type(self) -> str
+      def `NewKeyData` as new_key_data(self, key_template:KeyTemplate)
+        -> StatusOr<KeyData>
+
+    # Key Manager for HybridDecrypt.
+    class `CcKeyManager<HybridDecrypt>` as HybridDecryptKeyManager:
+      @classmethod
+      def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str)
+      -> StatusOr<HybridDecryptKeyManager>
+
+      def `GetPrimitive` as primitive(self, key_data:KeyData)
+        -> StatusOr<HybridDecrypt>
+      def `KeyType` as key_type(self) -> str
+      def `NewKeyData` as new_key_data(self, key_template:KeyTemplate)
+        -> StatusOr<KeyData>
+      def `GetPublicKeyData` as public_key_data(self, key_data:KeyData)
+        -> StatusOr<KeyData>
+
+    # Key Manager for HybridEncrypt.
+    class `CcKeyManager<HybridEncrypt>` as HybridEncryptKeyManager:
+      @classmethod
+      def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str)
+      -> StatusOr<HybridEncryptKeyManager>
+
+      def `GetPrimitive` as primitive(self, key_data:KeyData)
+        -> StatusOr<HybridEncrypt>
+      def `KeyType` as key_type(self) -> str
+      def `NewKeyData` as new_key_data(self, key_template:KeyTemplate)
+        -> StatusOr<KeyData>
+
+    # Key Manager for MAC (message authentication code).
+    class `CcKeyManager<Mac>` as MacKeyManager:
+      @classmethod
+      def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str)
+      -> StatusOr<MacKeyManager>
+
+      def `GetPrimitive` as primitive(self, key_data:KeyData)
+        -> StatusOr<Mac>
+      def `KeyType` as key_type(self) -> str
+      def `NewKeyData` as new_key_data(self, key_template:KeyTemplate)
+        -> StatusOr<KeyData>
+
+    # Key Manager for Public Key signing.
+    class `CcKeyManager<PublicKeySign>` as PublicKeySignKeyManager:
+      @classmethod
+      def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str)
+      -> StatusOr<PublicKeySignKeyManager>
+
+      def `GetPrimitive` as primitive(self, key_data: KeyData)
+        -> StatusOr<PublicKeySign>
+      def `KeyType` as key_type(self) -> str
+      def `NewKeyData` as new_key_data(self, key_template: KeyTemplate)
+        -> StatusOr<KeyData>
+      def `GetPublicKeyData` as public_key_data(self, key_data:KeyData)
+        -> StatusOr<KeyData>
+
+    # Key Manager for Public Key verification.
+    class `CcKeyManager<PublicKeyVerify>` as PublicKeyVerifyKeyManager:
+      @classmethod
+      def `GetFromCcRegistry`as from_cc_registry(cls, type_url: str)
+      -> StatusOr<PublicKeyVerifyKeyManager>
+
+      def `GetPrimitive` as primitive(self, key_data: KeyData)
+        -> StatusOr<PublicKeyVerify>
+      def `KeyType` as key_type(self) -> str
+      def `NewKeyData` as new_key_data(self, key_template: KeyTemplate)
+        -> StatusOr<KeyData>
diff --git a/python/cc/clif/cc_key_manager_test.py b/python/cc/clif/cc_key_manager_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..82d5543403214de9db028223205cae004a794ff9
--- /dev/null
+++ b/python/cc/clif/cc_key_manager_test.py
@@ -0,0 +1,331 @@
+# 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.python.cc.clif.py_key_manager."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+from tink.proto import aes_eax_pb2
+from tink.proto import aes_siv_pb2
+from tink.proto import common_pb2
+from tink.proto import ecdsa_pb2
+from tink.proto import ecies_aead_hkdf_pb2
+from tink.proto import hmac_pb2
+from tink.proto import tink_pb2
+from tink.python.aead import aead_key_templates
+from tink.python.cc.clif import cc_key_manager
+from tink.python.cc.clif import cc_tink_config
+from tink.python.hybrid import hybrid_key_templates
+from tink.util import error
+
+
+def setUpModule():
+  cc_tink_config.register()
+
+
+class AeadKeyManagerTest(googletest.TestCase):
+
+  def setUp(self):
+    super(AeadKeyManagerTest, self).setUp()
+    self.key_manager = cc_key_manager.AeadKeyManager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.AesEaxKey')
+
+  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 test_key_type(self):
+    self.assertEqual(self.key_manager.key_type(),
+                     'type.googleapis.com/google.crypto.tink.AesEaxKey')
+
+  def test_new_key_data(self):
+    key_template = self.new_aes_eax_key_template(12, 16)
+    key_data = self.key_manager.new_key_data(key_template)
+    self.assertEqual(key_data.type_url, self.key_manager.key_type())
+    self.assertEqual(key_data.key_material_type, tink_pb2.KeyData.SYMMETRIC)
+    key = aes_eax_pb2.AesEaxKey()
+    key.ParseFromString(key_data.value)
+    self.assertEqual(key.version, 0)
+    self.assertEqual(key.params.iv_size, 12)
+    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.assertRaises(error.StatusNotOk):
+      self.key_manager.new_key_data(key_template)
+
+  def test_encrypt_decrypt(self):
+    aead = self.key_manager.primitive(
+        self.key_manager.new_key_data(self.new_aes_eax_key_template(12, 16)))
+    plaintext = b'plaintext'
+    associated_data = b'associated_data'
+    ciphertext = aead.encrypt(plaintext, associated_data)
+    self.assertEqual(aead.decrypt(ciphertext, associated_data), plaintext)
+
+
+class DeterministicAeadKeyManagerTest(googletest.TestCase):
+
+  def setUp(self):
+    super(DeterministicAeadKeyManagerTest, self).setUp()
+    daead_key_manager = cc_key_manager.DeterministicAeadKeyManager
+    self.key_manager = daead_key_manager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.AesSivKey')
+
+  def new_aes_siv_key_template(self, key_size):
+    key_format = aes_siv_pb2.AesSivKeyFormat()
+    key_format.key_size = key_size
+    key_template = tink_pb2.KeyTemplate()
+    key_template.type_url = 'type.googleapis.com/google.crypto.tink.AesSivKey'
+    key_template.value = key_format.SerializeToString()
+    return key_template
+
+  def test_key_type(self):
+    self.assertEqual(self.key_manager.key_type(),
+                     'type.googleapis.com/google.crypto.tink.AesSivKey')
+
+  def test_new_key_data(self):
+    key_template = self.new_aes_siv_key_template(64)
+    key_data = self.key_manager.new_key_data(key_template)
+    self.assertEqual(key_data.type_url, self.key_manager.key_type())
+    self.assertEqual(key_data.key_material_type, tink_pb2.KeyData.SYMMETRIC)
+    key = aes_siv_pb2.AesSivKey()
+    key.ParseFromString(key_data.value)
+    self.assertEqual(key.version, 0)
+    self.assertLen(key.key_value, 64)
+
+  def test_invalid_params_throw_exception(self):
+    key_template = self.new_aes_siv_key_template(65)
+    with self.assertRaises(error.StatusNotOk):
+      self.key_manager.new_key_data(key_template)
+
+  def test_encrypt_decrypt(self):
+    aead = self.key_manager.primitive(
+        self.key_manager.new_key_data(self.new_aes_siv_key_template(64)))
+    plaintext = b'plaintext'
+    associated_data = b'associated_data'
+    ciphertext = aead.encrypt_deterministically(plaintext, associated_data)
+    self.assertEqual(
+        aead.decrypt_deterministically(ciphertext, associated_data), plaintext)
+
+
+class HybridKeyManagerTest(googletest.TestCase):
+
+  def hybrid_decrypt_key_manager(self):
+    return cc_key_manager.HybridDecryptKeyManager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey')
+
+  def hybrid_encrypt_key_manager(self):
+    return cc_key_manager.HybridEncryptKeyManager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey')
+
+  def test_new_key_data(self):
+    key_manager = self.hybrid_decrypt_key_manager()
+    key_data = key_manager.new_key_data(
+        hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
+    self.assertEqual(key_data.type_url, key_manager.key_type())
+    self.assertEqual(key_data.key_material_type,
+                     tink_pb2.KeyData.ASYMMETRIC_PRIVATE)
+    key = ecies_aead_hkdf_pb2.EciesAeadHkdfPrivateKey()
+    key.ParseFromString(key_data.value)
+    self.assertLen(key.key_value, 32)
+    self.assertEqual(key.public_key.params.kem_params.curve_type,
+                     common_pb2.NIST_P256)
+
+  def test_new_key_data_invalid_params_throw_exception(self):
+    with self.assertRaisesRegex(error.StatusNotOk,
+                                'Unsupported elliptic curve'):
+      self.hybrid_decrypt_key_manager().new_key_data(
+          hybrid_key_templates.create_ecies_aead_hkdf_key_template(
+              curve_type=100,
+              ec_point_format=common_pb2.UNCOMPRESSED,
+              hash_type=common_pb2.SHA256,
+              dem_key_template=aead_key_templates.AES128_GCM))
+
+  def test_encrypt_decrypt(self):
+    decrypt_key_manager = self.hybrid_decrypt_key_manager()
+    encrypt_key_manager = self.hybrid_encrypt_key_manager()
+    key_data = decrypt_key_manager.new_key_data(
+        hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
+    public_key_data = decrypt_key_manager.public_key_data(key_data)
+    hybrid_encrypt = encrypt_key_manager.primitive(public_key_data)
+    ciphertext = hybrid_encrypt.encrypt(b'some plaintext', b'some context info')
+    hybrid_decrypt = decrypt_key_manager.primitive(key_data)
+    self.assertEqual(hybrid_decrypt.decrypt(ciphertext, b'some context info'),
+                     b'some plaintext')
+
+  def test_decrypt_fails(self):
+    decrypt_key_manager = self.hybrid_decrypt_key_manager()
+    key_data = decrypt_key_manager.new_key_data(
+        hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
+    hybrid_decrypt = decrypt_key_manager.primitive(key_data)
+    with self.assertRaisesRegex(error.StatusNotOk, 'ciphertext too short'):
+      hybrid_decrypt.decrypt(b'bad ciphertext', b'some context info')
+
+
+class MacKeyManagerTest(googletest.TestCase):
+
+  def setUp(self):
+    super(MacKeyManagerTest, self).setUp()
+    self.key_manager = cc_key_manager.MacKeyManager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.HmacKey')
+
+  def new_hmac_key_template(self, hash_type, tag_size, key_size):
+    key_format = hmac_pb2.HmacKeyFormat()
+    key_format.params.hash = hash_type
+    key_format.params.tag_size = tag_size
+    key_format.key_size = key_size
+    key_template = tink_pb2.KeyTemplate()
+    key_template.type_url = 'type.googleapis.com/google.crypto.tink.HmacKey'
+    key_template.value = key_format.SerializeToString()
+    return key_template
+
+  def test_key_type(self):
+    self.assertEqual(self.key_manager.key_type(),
+                     'type.googleapis.com/google.crypto.tink.HmacKey')
+
+  def test_new_key_data(self):
+    key_template = self.new_hmac_key_template(common_pb2.SHA256, 24, 16)
+    key_data = self.key_manager.new_key_data(key_template)
+    self.assertEqual(key_data.type_url, self.key_manager.key_type())
+    key = hmac_pb2.HmacKey()
+    key.ParseFromString(key_data.value)
+    self.assertEqual(key.version, 0)
+    self.assertEqual(key.params.hash, common_pb2.SHA256)
+    self.assertEqual(key.params.tag_size, 24)
+    self.assertLen(key.key_value, 16)
+
+  def test_invalid_params_throw_exception(self):
+    key_template = self.new_hmac_key_template(common_pb2.SHA256, 9, 16)
+    with self.assertRaises(error.StatusNotOk):
+      self.key_manager.new_key_data(key_template)
+
+  def test_mac_success(self):
+    mac = self.key_manager.primitive(
+        self.key_manager.new_key_data(
+            self.new_hmac_key_template(common_pb2.SHA256, 24, 16)))
+    data = b'data'
+    tag = mac.compute_mac(data)
+    self.assertLen(tag, 24)
+    # No exception raised.
+    mac.verify_mac(tag, data)
+
+  def test_mac_wrong(self):
+    mac = self.key_manager.primitive(
+        self.key_manager.new_key_data(
+            self.new_hmac_key_template(common_pb2.SHA256, 16, 16)))
+    with self.assertRaisesRegex(error.StatusNotOk, 'verification failed'):
+      mac.verify_mac(b'0123456789ABCDEF', b'data')
+
+
+class PublicKeySignVerifyKeyManagerTest(googletest.TestCase):
+
+  def setUp(self):
+    super(PublicKeySignVerifyKeyManagerTest, self).setUp()
+    public_key_verify_manager = cc_key_manager.PublicKeyVerifyKeyManager
+    self.key_manager_verify = public_key_verify_manager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.EcdsaPublicKey')
+    public_key_sign_manager = cc_key_manager.PublicKeySignKeyManager
+    self.key_manager_sign = public_key_sign_manager.from_cc_registry(
+        'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey')
+
+  def new_ecdsa_key_template(self, hash_type, curve_type, encoding,
+                             public=False):
+    params = ecdsa_pb2.EcdsaParams(
+        hash_type=hash_type, curve=curve_type, encoding=encoding)
+    key_format = ecdsa_pb2.EcdsaKeyFormat(params=params)
+    key_template = tink_pb2.KeyTemplate()
+    if public:
+      append = 'EcdsaPublicKey'
+    else:
+      append = 'EcdsaPrivateKey'
+    key_template.type_url = 'type.googleapis.com/google.crypto.tink.' + append
+
+    key_template.value = key_format.SerializeToString()
+    return key_template
+
+  def test_key_type_sign(self):
+    self.assertEqual(self.key_manager_sign.key_type(),
+                     'type.googleapis.com/google.crypto.tink.EcdsaPrivateKey')
+
+  def test_key_type_verify(self):
+    self.assertEqual(self.key_manager_verify.key_type(),
+                     'type.googleapis.com/google.crypto.tink.EcdsaPublicKey')
+
+  def test_new_key_data_sign(self):
+    key_template = self.new_ecdsa_key_template(
+        common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER)
+    key_data = self.key_manager_sign.new_key_data(key_template)
+    self.assertEqual(key_data.type_url, self.key_manager_sign.key_type())
+    key = ecdsa_pb2.EcdsaPrivateKey()
+    key.ParseFromString(key_data.value)
+    public_key = key.public_key
+    self.assertEqual(key.version, 0)
+    self.assertEqual(public_key.version, 0)
+    self.assertEqual(public_key.params.hash_type, common_pb2.SHA256)
+    self.assertEqual(public_key.params.curve, common_pb2.NIST_P256)
+    self.assertEqual(public_key.params.encoding, ecdsa_pb2.DER)
+    self.assertLen(key.key_value, 32)
+
+  def test_new_key_data_verify(self):
+    key_template = self.new_ecdsa_key_template(
+        common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER, True)
+    with self.assertRaisesRegex(error.StatusNotOk, 'Operation not supported'):
+      self.key_manager_verify.new_key_data(key_template)
+
+  def test_signature_success(self):
+    priv_key = self.key_manager_sign.new_key_data(
+        self.new_ecdsa_key_template(common_pb2.SHA256, common_pb2.NIST_P256,
+                                    ecdsa_pb2.DER))
+    pub_key = self.key_manager_sign.public_key_data(priv_key)
+
+    verifier = self.key_manager_verify.primitive(pub_key)
+    signer = self.key_manager_sign.primitive(priv_key)
+
+    data = b'data'
+    signature = signer.sign(data)
+
+    # Starts with a DER sequence
+    self.assertEqual(bytearray(signature)[0], 0x30)
+
+    verifier.verify(signature, data)
+
+  def test_signature_fails(self):
+    key_template = self.new_ecdsa_key_template(
+        common_pb2.SHA256, common_pb2.NIST_P256, ecdsa_pb2.DER, False)
+    priv_key = self.key_manager_sign.new_key_data(key_template)
+    pub_key = self.key_manager_sign.public_key_data(priv_key)
+
+    signer = self.key_manager_sign.primitive(priv_key)
+    verifier = self.key_manager_verify.primitive(pub_key)
+
+    data = b'data'
+    signature = signer.sign(data)
+
+    with self.assertRaisesRegex(error.StatusNotOk, 'Signature is not valid'):
+      verifier.verify(signature, 'wrongdata')
+
+    with self.assertRaisesRegex(error.StatusNotOk, 'Signature is not valid'):
+      verifier.verify('wrongsignature', data)
+
+
+if __name__ == '__main__':
+  googletest.main()
diff --git a/python/cc/clif/cc_tink_config.clif b/python/cc/clif/cc_tink_config.clif
new file mode 100644
index 0000000000000000000000000000000000000000..0eff706413be02f03e2e6082c00cde097f9a6d07
--- /dev/null
+++ b/python/cc/clif/cc_tink_config.clif
@@ -0,0 +1,21 @@
+# 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.
+
+from "tink/proto/config_pyclif.h" import *  # RegistryConfig
+from "tink/python/util/clif.h" import *  # StatusOr
+
+from "tink/python/cc/cc_tink_config.h":
+  namespace `crypto::tink`:
+    def `CcTinkConfigRegister` as register() -> Status
+    def `CcTinkConfigLatest` as latest() -> RegistryConfig
diff --git a/python/cc/clif/cc_tink_config_test.py b/python/cc/clif/cc_tink_config_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..e7e96de54ad847ef09b99259609e17032d9236a0
--- /dev/null
+++ b/python/cc/clif/cc_tink_config_test.py
@@ -0,0 +1,43 @@
+# 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.python.cc.clif.cc_tink_config."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+from tink.python.cc.clif import cc_tink_config
+
+
+class CcTinkConfigTest(googletest.TestCase):
+
+  def test_latest(self):
+    cc_tink_config.register()
+    latest = cc_tink_config.latest()
+    primitive_names = {entry.primitive_name for entry in latest.entry}
+    self.assertIn('Aead', primitive_names)
+    self.assertIn('Mac', primitive_names)
+    self.assertIn('PublicKeySign', primitive_names)
+    type_urls = {entry.type_url for entry in latest.entry}
+    self.assertIn('type.googleapis.com/google.crypto.tink.AesEaxKey', type_urls)
+    self.assertIn('type.googleapis.com/google.crypto.tink.AesGcmKey', type_urls)
+    self.assertIn('type.googleapis.com/google.crypto.tink.HmacKey', type_urls)
+    self.assertIn('type.googleapis.com/google.crypto.tink.EcdsaPrivateKey',
+                  type_urls)
+
+
+if __name__ == '__main__':
+  googletest.main()
diff --git a/python/testing/helper.py b/python/testing/helper.py
new file mode 100644
index 0000000000000000000000000000000000000000..2bd723467c0bd01c539e61d86af193d1eeb65da8
--- /dev/null
+++ b/python/testing/helper.py
@@ -0,0 +1,149 @@
+# Copyright 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This class implements helper functions for testing."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from typing import Text
+
+from tink.proto import tink_pb2
+from tink.python import aead
+from tink.python import daead
+from tink.python import hybrid
+from tink.python import mac
+from tink.python import signature as pk_signature
+from tink.python.core import tink_error
+
+
+def fake_key(value: bytes = b'fakevalue',
+             type_url: Text = 'fakeurl',
+             key_material_type: tink_pb2.KeyData.KeyMaterialType = tink_pb2
+             .KeyData.SYMMETRIC,
+             key_id: int = 1234,
+             status: tink_pb2.KeyStatusType = tink_pb2.ENABLED,
+             output_prefix_type: tink_pb2.OutputPrefixType = tink_pb2.TINK
+            ) -> tink_pb2.Keyset.Key:
+  """Returns a fake but valid key."""
+  key = tink_pb2.Keyset.Key(
+      key_id=key_id,
+      status=status,
+      output_prefix_type=output_prefix_type)
+  key.key_data.type_url = type_url
+  key.key_data.value = value
+  key.key_data.key_material_type = key_material_type
+  return key
+
+
+class FakeMac(mac.Mac):
+  """A fake MAC implementation."""
+
+  def __init__(self, name: Text = 'FakeMac'):
+    self._name = name
+
+  def compute_mac(self, data: bytes) -> bytes:
+    return data + b'|' + self._name.encode()
+
+  def verify_mac(self, mac_value: bytes, data: bytes) -> None:
+    if mac_value != data + b'|' + self._name.encode():
+      raise tink_error.TinkError('invalid mac ' + mac_value.decode())
+
+
+class FakeAead(aead.Aead):
+  """A fake AEAD implementation."""
+
+  def __init__(self, name: Text = 'FakeAead'):
+    self._name = name
+
+  def encrypt(self, plaintext: bytes, associated_data: bytes) -> bytes:
+    return plaintext + b'|' + associated_data + b'|' + self._name.encode()
+
+  def decrypt(self, ciphertext: bytes, associated_data: bytes) -> bytes:
+    data = ciphertext.split(b'|')
+    if (len(data) < 3 or data[1] != associated_data or
+        data[2] != self._name.encode()):
+      raise tink_error.TinkError('failed to decrypt ciphertext ' +
+                                 ciphertext.decode())
+    return data[0]
+
+
+class FakeDeterministicAead(daead.DeterministicAead):
+  """A fake Deterministic AEAD implementation."""
+
+  def __init__(self, name: Text = 'FakeDeterministicAead'):
+    self._name = name
+
+  def encrypt_deterministically(self, plaintext: bytes,
+                                associated_data: bytes) -> bytes:
+    return plaintext + b'|' + associated_data + b'|' + self._name.encode()
+
+  def decrypt_deterministically(self, ciphertext: bytes,
+                                associated_data: bytes) -> bytes:
+    data = ciphertext.split(b'|')
+    if (len(data) < 3 or
+        data[1] != associated_data or
+        data[2] != self._name.encode()):
+      raise tink_error.TinkError('failed to decrypt ciphertext ' +
+                                 ciphertext.decode())
+    return data[0]
+
+
+class FakeHybridDecrypt(hybrid.HybridDecrypt):
+  """A fake HybridEncrypt implementation."""
+
+  def __init__(self, name: Text = 'Hybrid'):
+    self._name = name
+
+  def decrypt(self, ciphertext: bytes, context_info: bytes) -> bytes:
+    data = ciphertext.split(b'|')
+    if (len(data) < 3 or
+        data[1] != context_info or
+        data[2] != self._name.encode()):
+      raise tink_error.TinkError('failed to decrypt ciphertext ' +
+                                 ciphertext.decode())
+    return data[0]
+
+
+class FakeHybridEncrypt(hybrid.HybridEncrypt):
+  """A fake HybridEncrypt implementation."""
+
+  def __init__(self, name: Text = 'Hybrid'):
+    self._name = name
+
+  def encrypt(self, plaintext: bytes, context_info: bytes) -> bytes:
+    return plaintext + b'|' + context_info + b'|' + self._name.encode()
+
+
+class FakePublicKeySign(pk_signature.PublicKeySign):
+  """A fake PublicKeySign implementation."""
+
+  def __init__(self, name: Text = 'FakePublicKeySign'):
+    self._name = name
+
+  def sign(self, data: bytes) -> bytes:
+    return data + b'|' + self._name.encode()
+
+
+class FakePublicKeyVerify(pk_signature.PublicKeyVerify):
+  """A fake PublicKeyVerify implementation."""
+
+  def __init__(self, name: Text = 'FakePublicKeyVerify'):
+    self._name = name
+
+  def verify(self, signature: bytes, data: bytes):
+    if signature != data + b'|' + self._name.encode():
+      raise tink_error.TinkError('invalid signature ' + signature.decode())
diff --git a/python/testing/helper_test.py b/python/testing/helper_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..385e4b30f2f4bfc7b002f32cf2306f8c0123317b
--- /dev/null
+++ b/python/testing/helper_test.py
@@ -0,0 +1,139 @@
+# 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.python.testing.helper."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+from tink.python.core import tink_error
+from tink.python.testing import helper
+
+
+class HelperTest(googletest.TestCase):
+
+  def test_fake_mac_success(self):
+    mac = helper.FakeMac('Name')
+    mac_value = mac.compute_mac(b'data')
+    mac.verify_mac(mac_value, b'data')
+
+  def test_fake_mac_fail_wrong_data(self):
+    mac = helper.FakeMac('Name')
+    mac_value = mac.compute_mac(b'data')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'invalid mac'):
+      mac.verify_mac(mac_value, b'wrong data')
+
+  def test_fake_mac_fail_wrong_primitive(self):
+    mac = helper.FakeMac('Name')
+    mac_value = mac.compute_mac(b'data')
+    wrong_mac = helper.FakeMac('Wrong Name')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'invalid mac'):
+      wrong_mac.verify_mac(mac_value, b'data')
+
+  def test_fake_aead_success(self):
+    aead = helper.FakeAead('Name')
+    ciphertext = aead.encrypt(b'plaintext', b'associated_data')
+    self.assertEqual(
+        aead.decrypt(ciphertext, b'associated_data'),
+        b'plaintext')
+
+  def test_fake_aead_fail_wrong_cipertext(self):
+    aead = helper.FakeAead('Name')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'failed to decrypt ciphertext'):
+      aead.decrypt(b'wrong ciphertext', b'associated_data')
+
+  def test_fake_aead_fail_wrong_associated_data(self):
+    aead = helper.FakeAead('Name')
+    ciphertext = aead.encrypt(b'plaintext', b'associated_data')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'failed to decrypt ciphertext'):
+      aead.decrypt(ciphertext, b'wrong_associated_data')
+
+  def test_fake_aead_fail_wrong_primitive(self):
+    aead = helper.FakeAead('Name')
+    ciphertext = aead.encrypt(b'plaintext', b'associated_data')
+    wrong_aead = helper.FakeAead('Wrong Name')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'failed to decrypt ciphertext'):
+      wrong_aead.decrypt(ciphertext, b'associated_data')
+
+  def test_fake_deterministic_aead_success(self):
+    daead = helper.FakeDeterministicAead('Name')
+    ciphertext = daead.encrypt_deterministically(b'plaintext',
+                                                 b'associated_data')
+    self.assertEqual(
+        daead.decrypt_deterministically(ciphertext, b'associated_data'),
+        b'plaintext')
+
+  def test_fake_deterministic_aead_fail_wrong_cipertext(self):
+    daead = helper.FakeDeterministicAead('Name')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'failed to decrypt ciphertext'):
+      daead.decrypt_deterministically(b'wrong ciphertext', b'associated_data')
+
+  def test_fake_deterministic_aead_fail_wrong_associated_data(self):
+    daead = helper.FakeDeterministicAead('Name')
+    ciphertext = daead.encrypt_deterministically(b'plaintext',
+                                                 b'associated_data')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'failed to decrypt ciphertext'):
+      daead.decrypt_deterministically(ciphertext, b'wrong_associated_data')
+
+  def test_fake_deterministic_aead_fail_wrong_primitive(self):
+    daead = helper.FakeDeterministicAead('Name')
+    ciphertext = daead.encrypt_deterministically(b'plaintext',
+                                                 b'associated_data')
+    wrong_daead = helper.FakeDeterministicAead('Wrong Name')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'failed to decrypt ciphertext'):
+      wrong_daead.decrypt_deterministically(ciphertext, b'associated_data')
+
+  def test_fake_hybrid_success(self):
+    enc = helper.FakeHybridEncrypt('Name')
+    dec = helper.FakeHybridDecrypt('Name')
+    ciphertext = enc.encrypt(b'plaintext', b'context_info')
+    self.assertEqual(
+        dec.decrypt(ciphertext, b'context_info'),
+        b'plaintext')
+
+  def test_fake_hybrid_fail_wrong_context(self):
+    enc = helper.FakeHybridEncrypt('Name')
+    dec = helper.FakeHybridDecrypt('Name')
+    ciphertext = enc.encrypt(b'plaintext', b'context_info')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'failed to decrypt ciphertext'):
+      dec.decrypt(ciphertext, b'other_context_info')
+
+  def test_fake_hybrid_fail_wrong_dec(self):
+    enc = helper.FakeHybridEncrypt('Name')
+    dec = helper.FakeHybridDecrypt('Wrong Name')
+    ciphertext = enc.encrypt(b'plaintext', b'context_info')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'failed to decrypt ciphertext'):
+      dec.decrypt(ciphertext, b'context_info')
+
+  def test_fake_hybrid_fail_wrong_ciphertext(self):
+    dec = helper.FakeHybridDecrypt('Name')
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'failed to decrypt ciphertext'):
+      dec.decrypt(b'wrong ciphertext', b'context_info')
+
+
+if __name__ == '__main__':
+  googletest.main()
diff --git a/python/util/clif.cc b/python/util/clif.cc
new file mode 100644
index 0000000000000000000000000000000000000000..c440e11d3424283671b28b322be5253816f9bfd6
--- /dev/null
+++ b/python/util/clif.cc
@@ -0,0 +1,131 @@
+// Copyright 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "tink/python/util/clif.h"
+
+#include "google/protobuf/message_set.pb.h"
+#include "absl/strings/str_cat.h"
+#include "tink/util/canonical_errors.h"
+
+#if !defined(PORTABLE_STATUS)
+#include "tink/util/status_payload.h"
+#endif
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+PyObject* Get_UtilStatusOk() {
+  static PyObject* const kUtilStatusOk = _PyObject_New(&PyBaseObject_Type);
+  // TODO(mrovner): The following return is sligthly wrong:
+  // In the (unlikely) case where the allocation failed, subsequent calls to
+  // this function will return null without setting a Python exception.
+  Py_XINCREF(kUtilStatusOk);
+  return kUtilStatusOk;
+}
+
+void ErrorFromStatus(const Status& status) {
+  /* Can't just do this
+       static PyObject* const kUtilStatusError = ImportFQName(...
+     because static creates a C++ lock around the whole statement and GIL can be
+     elsewhere due to Python import. */
+  static PyObject* kUtilStatusError = nullptr;
+  if (kUtilStatusError == nullptr) {
+    // Worst case it will do ImportFQName K times (K==number of threads)
+    // which is OK.
+    kUtilStatusError =
+        clif::ImportFQName("google3.util.task.python.error.StatusNotOk");
+    DCHECK(kUtilStatusError != nullptr);
+  }
+  PyObject* message_set_object;
+  message_set_object = Py_None;
+  Py_INCREF(message_set_object);
+  PyObject* err = Py_BuildValue(
+      "is#siN", status.error_code(), status.error_message().data(),
+      status.error_message().size(), "Not implemented!",
+      status.CanonicalCode(), message_set_object);
+  if (err != nullptr) {
+    PyErr_SetObject(kUtilStatusError, err);
+    Py_DECREF(err);
+  }  // otherwise error is already set
+}
+
+Status StatusFromPyException() {
+  if (!PyErr_Occurred()) {
+    return OkStatus();
+  }
+
+  if (PyErr_ExceptionMatches(PyExc_MemoryError)) {
+    return ::util::ResourceExhaustedError(PyExcFetch());
+  }
+  if (PyErr_ExceptionMatches(PyExc_NotImplementedError)) {
+    return ::util::UnimplementedError(PyExcFetch());
+  }
+  if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
+    return ::util::AbortedError(PyExcFetch());
+  }
+  if (PyErr_ExceptionMatches(PyExc_SystemError) ||
+      PyErr_ExceptionMatches(PyExc_SyntaxError)) {
+    return ::util::InternalError(PyExcFetch());
+  }
+  if (PyErr_ExceptionMatches(PyExc_TypeError)) {
+    return ::util::InvalidArgumentError(PyExcFetch());
+  }
+  if (PyErr_ExceptionMatches(PyExc_ValueError)) {
+    return ::util::OutOfRangeError(PyExcFetch());
+  }
+  if (PyErr_ExceptionMatches(PyExc_LookupError)) {
+    return ::util::NotFoundError(PyExcFetch());
+  }
+
+  return ::util::UnknownError(PyExcFetch());
+}
+
+PyObject* Clif_PyObjFrom(const Status& c, const clif::py::PostConv& unused) {
+  if (!c.ok()) {
+    ErrorFromStatus(c);
+    return nullptr;
+  }
+  return Get_UtilStatusOk();
+}
+
+std::string PyExcFetch() {
+  CHECK(PyErr_Occurred()) << "Must only call PyExcFetch after an exception.";
+  PyObject* ptype;
+  PyObject* pvalue;
+  PyObject* ptraceback;
+  PyErr_Fetch(&ptype, &pvalue, &ptraceback);
+  std::string err = clif::ClassName(ptype);
+  if (pvalue) {
+    PyObject* str = PyObject_Str(pvalue);
+    if (str) {
+#if PY_MAJOR_VERSION < 3
+      absl::StrAppend(&err, ": ", PyString_AS_STRING(str));
+#else
+      absl::StrAppend(&err, ": ", PyUnicode_AsUTF8(str));
+#endif
+      Py_DECREF(str);
+    }
+    Py_DECREF(pvalue);
+  }
+  Py_DECREF(ptype);
+  Py_XDECREF(ptraceback);
+  return err;
+}
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
diff --git a/python/util/clif.h b/python/util/clif.h
new file mode 100644
index 0000000000000000000000000000000000000000..6137558ab29f2d9412f8af4c7fb48b6ec41f2e8b
--- /dev/null
+++ b/python/util/clif.h
@@ -0,0 +1,126 @@
+// Copyright 2019 Google LLC.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#ifndef TINK_PYTHON_UTIL_CLIF_H_
+#define TINK_PYTHON_UTIL_CLIF_H_
+
+#include "base/logging.h"
+#include "devtools/clif/python/types.h"
+#include "util/python/pyobject.h"
+#include "tink/util/status.h"
+#include "tink/util/statusor.h"
+
+// CLIF use `::crypto::tink::util::Status` as Status
+// CLIF use `::crypto::tink::util::StatusOr` as StatusOr
+
+namespace crypto {
+namespace tink {
+namespace util {
+
+PyObject* Get_UtilStatusOk();
+void ErrorFromStatus(const Status& status);
+Status StatusFromPyException();
+std::string PyExcFetch();
+
+PyObject* Clif_PyObjFrom(const Status& c, const ::clif::py::PostConv&);
+
+// Note: there is no corresponding PyObjAs for util::Status, as this class is
+// represented on the Python side by an exception. This implies that it is not
+// possible to hand a util::Status to C++. Pass a code and message instead.
+
+template <typename T>
+PyObject* Clif_PyObjFrom(const StatusOr<T>& c, const ::clif::py::PostConv& pc) {
+  if (!c.ok()) {
+    ErrorFromStatus(c.status());
+    return nullptr;
+  } else {
+    using ::clif::Clif_PyObjFrom;
+    return Clif_PyObjFrom(c.ValueOrDie(), pc.Get(0));
+  }
+}
+
+template <typename T>
+PyObject* Clif_PyObjFrom(StatusOr<T>&& c,  // NOLINT:c++11
+                         const ::clif::py::PostConv& pc) {
+  if (!c.ok()) {
+    ErrorFromStatus(c.status());
+    return nullptr;
+  } else {
+    using ::clif::Clif_PyObjFrom;
+    return Clif_PyObjFrom(std::move(c).ValueOrDie(), pc.Get(0));
+  }
+}
+
+template<typename T>
+bool Clif_PyObjAs(PyObject* p, StatusOr<T>* c) {
+  CHECK(c != nullptr);
+
+  if (PyErr_Occurred()) {
+    *c = StatusFromPyException();
+    return true;
+  }
+
+  T val;
+  using ::clif::Clif_PyObjAs;
+  if (Clif_PyObjAs(p, &val)) {
+    *c = std::move(val);
+    return true;
+  }
+
+  return false;
+}
+
+}  // namespace util
+}  // namespace tink
+}  // namespace crypto
+
+namespace clif {
+namespace callback {
+
+// Specialization of a generic Clif Python callback handling for a
+// function returning util::StatusOr.
+template <typename T>
+class ReturnValue<::crypto::tink::util::StatusOr<T>> {
+ public:
+  ::crypto::tink::util::StatusOr<T> FromPyValue(PyObject* result) {
+    ::crypto::tink::util::StatusOr<T> v;
+    bool ok = Clif_PyObjAs(result, &v);
+    Py_XDECREF(result);
+    if (!ok) {
+      v = ::crypto::tink::util::StatusFromPyException();
+    }
+    return v;
+  }
+};
+
+// Specialization of a generic Clif Python callback handling for a
+// function returning a util::Status.
+template <>
+class ReturnValue<::crypto::tink::util::Status> {
+ public:
+  ::crypto::tink::util::Status FromPyValue(PyObject* result) {
+    Py_XDECREF(result);
+    if (PyErr_Occurred()) {
+      return ::crypto::tink::util::StatusFromPyException();
+    }
+    return ::crypto::tink::util::OkStatus();
+  }
+};
+
+}  // namespace callback
+}  // namespace clif
+
+#endif  // TINK_PYTHON_UTIL_CLIF_H_