diff --git a/python/hybrid/__init__.py b/python/hybrid/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..53c71a98499d4477905050febc4ffc9f4b64e591
--- /dev/null
+++ b/python/hybrid/__init__.py
@@ -0,0 +1,33 @@
+# 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.
+
+"""Hybrid package."""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from tink.python.hybrid import hybrid_decrypt
+from tink.python.hybrid import hybrid_decrypt_key_manager
+from tink.python.hybrid import hybrid_decrypt_wrapper
+from tink.python.hybrid import hybrid_encrypt
+from tink.python.hybrid import hybrid_encrypt_key_manager
+from tink.python.hybrid import hybrid_encrypt_wrapper
+from tink.python.hybrid import hybrid_key_templates
+
+
+HybridDecrypt = hybrid_decrypt.HybridDecrypt
+HybridEncrypt = hybrid_encrypt.HybridEncrypt
+HybridDecryptWrapper = hybrid_decrypt_wrapper.HybridDecryptWrapper
+HybridEncryptWrapper = hybrid_encrypt_wrapper.HybridEncryptWrapper
diff --git a/python/hybrid/hybrid_decrypt.py b/python/hybrid/hybrid_decrypt.py
new file mode 100644
index 0000000000000000000000000000000000000000..cfb3ead76829e59a0dd2a848a77eb6b6eeb53460
--- /dev/null
+++ b/python/hybrid/hybrid_decrypt.py
@@ -0,0 +1,62 @@
+# Copyright 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This module defines the interface for HybridDecrypt."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+import abc
+
+
+class HybridDecrypt(object):
+  """The interface for hybrid decryption.
+
+  Implementations of this interface are secure against adaptive
+  chosen ciphertext attacks.  In addition to 'plaintext' the
+  encryption takes an extra parameter 'context_info', which usually
+  is public data implicit from the context, but should be bound to
+  the resulting ciphertext: upon decryption the ciphertext allows for
+  checking the integrity of 'context_info' (but there are no
+  guarantees wrt. to secrecy or authenticity of 'context_info').
+
+  WARNING: hybrid encryption does not provide authenticity, that is the
+  recipient of an encrypted message does not know the identity of the sender.
+  Similar to general public-key encryption schemes the security goal of
+  hybrid encryption is to provide privacy only. In other words, hybrid
+  encryption is secure if and only if the recipient can accept anonymous
+  messages or can rely on other mechanisms to authenticate the sender.
+
+  'context_info' can be empty or null, but to ensure the correct
+  decryption of the resulting ciphertext the same value must be
+  provided for decryption operation (cf. HybridDecrypt-interface).
+
+  A concrete instantiation of this interface can implement the
+  binding of 'context_info' to the ciphertext in various ways, for
+  example:
+
+  - use 'context_info' as "associated data"-input for the employed
+    AEAD symmetric encryption (cf. https://tools.ietf.org/html/rfc5116).
+  - use 'context_info' as "CtxInfo"-input for HKDF (if the implementation uses
+    HKDF as key derivation function, cf. https://tools.ietf.org/html/rfc5869).
+  """
+
+  __metaclass__ = abc.ABCMeta
+
+  @abc.abstractmethod
+  def decrypt(self, ciphertext: bytes, context_info: bytes) -> bytes:
+    """Decrypts ciphertext verifying the integrity of context_info."""
+    raise NotImplementedError()
diff --git a/python/hybrid/hybrid_decrypt_key_manager.py b/python/hybrid/hybrid_decrypt_key_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..fea350085b5bdf703c0e7c11a02f28487914c72f
--- /dev/null
+++ b/python/hybrid/hybrid_decrypt_key_manager.py
@@ -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.
+
+"""Python wrapper of the CLIF-wrapped C++ Hybrid En- and Decryption key manager."""
+
+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.cc.python import hybrid_decrypt as cc_hybrid_decrypt
+from tink.python.cc.clif import cc_key_manager
+from tink.python.core import key_manager
+from tink.python.core import tink_error
+from tink.python.hybrid import hybrid_decrypt
+
+
+class _HybridDecryptCcToPyWrapper(hybrid_decrypt.HybridDecrypt):
+  """Transforms cliffed C++ HybridDecrypt primitive into a Python primitive."""
+
+  def __init__(self, cc_primitive: cc_hybrid_decrypt.HybridDecrypt):
+    self._hybrid_decrypt = cc_primitive
+
+  @tink_error.use_tink_errors
+  def decrypt(self, ciphertext: bytes, context_info: bytes) -> bytes:
+    return self._hybrid_decrypt.decrypt(ciphertext, context_info)
+
+
+def from_cc_registry(
+    type_url: Text) -> key_manager.KeyManager[hybrid_decrypt.HybridDecrypt]:
+  return key_manager.PrivateKeyManagerCcToPyWrapper(
+      cc_key_manager.HybridDecryptKeyManager.from_cc_registry(type_url),
+      hybrid_decrypt.HybridDecrypt, _HybridDecryptCcToPyWrapper)
diff --git a/python/hybrid/hybrid_decrypt_wrapper.py b/python/hybrid/hybrid_decrypt_wrapper.py
new file mode 100644
index 0000000000000000000000000000000000000000..5b75660b772a832f3fa5825785c6b156df18209b
--- /dev/null
+++ b/python/hybrid/hybrid_decrypt_wrapper.py
@@ -0,0 +1,74 @@
+# 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.
+
+"""HybridDecrypt wrapper."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from typing import Type
+
+from pyglib import logging
+from tink.python.core import crypto_format
+from tink.python.core import primitive_set
+from tink.python.core import primitive_wrapper
+from tink.python.core import tink_error
+from tink.python.hybrid import hybrid_decrypt
+
+
+class _WrappedHybridDecrypt(hybrid_decrypt.HybridDecrypt):
+  """Implements HybridDecrypt for a set of HybridDecrypt primitives."""
+
+  def __init__(self, pset: primitive_set.PrimitiveSet):
+    self._primitive_set = pset
+
+  def decrypt(self, ciphertext: bytes, context_info: bytes) -> bytes:
+    if len(ciphertext) > crypto_format.NON_RAW_PREFIX_SIZE:
+      prefix = ciphertext[:crypto_format.NON_RAW_PREFIX_SIZE]
+      ciphertext_no_prefix = ciphertext[crypto_format.NON_RAW_PREFIX_SIZE:]
+      for entry in self._primitive_set.primitive_from_identifier(prefix):
+        try:
+          return entry.primitive.decrypt(ciphertext_no_prefix,
+                                         context_info)
+        except tink_error.TinkError as e:
+          logging.info(
+              'ciphertext prefix matches a key, but cannot decrypt: %s', e)
+    # Let's try all RAW keys.
+    for entry in self._primitive_set.raw_primitives():
+      try:
+        return entry.primitive.decrypt(ciphertext, context_info)
+      except tink_error.TinkError as e:
+        pass
+    # nothing works.
+    raise tink_error.TinkError('Decryption failed.')
+
+
+class HybridDecryptWrapper(
+    primitive_wrapper.PrimitiveWrapper[hybrid_decrypt.HybridDecrypt]):
+  """HybridDecryptWrapper is the PrimitiveWrapper for HybridDecrypt.
+
+  The returned primitive works with a keyset (rather than a single key). To
+  decrypt, the primitive uses the prefix of the ciphertext to efficiently select
+  the right key in the set. If the keys associated with the prefix do not work,
+  the primitive tries all keys with OutputPrefixType RAW.
+  """
+
+  def wrap(self,
+           pset: primitive_set.PrimitiveSet) -> hybrid_decrypt.HybridDecrypt:
+    return _WrappedHybridDecrypt(pset)
+
+  def primitive_class(self) -> Type[hybrid_decrypt.HybridDecrypt]:
+    return hybrid_decrypt.HybridDecrypt
diff --git a/python/hybrid/hybrid_encrypt.py b/python/hybrid/hybrid_encrypt.py
new file mode 100644
index 0000000000000000000000000000000000000000..584259fe595a9bb5e35102622fdb823d0d6d2b00
--- /dev/null
+++ b/python/hybrid/hybrid_encrypt.py
@@ -0,0 +1,62 @@
+# Copyright 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""This module defines the interface for HybridEncrypt."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+import abc
+
+
+class HybridEncrypt(object):
+  """The interface for hybrid encryption.
+
+  Implementations of this interface are secure against adaptive
+  chosen ciphertext attacks.  In addition to 'plaintext' the
+  encryption takes an extra parameter 'context_info', which usually
+  is public data implicit from the context, but should be bound to
+  the resulting ciphertext: upon decryption the ciphertext allows for
+  checking the integrity of 'context_info' (but there are no
+  guarantees wrt. to secrecy or authenticity of 'context_info').
+
+  WARNING: hybrid encryption does not provide authenticity, that is the
+  recipient of an encrypted message does not know the identity of the sender.
+  Similar to general public-key encryption schemes the security goal of
+  hybrid encryption is to provide privacy only. In other words, hybrid
+  encryption is secure if and only if the recipient can accept anonymous
+  messages or can rely on other mechanisms to authenticate the sender.
+
+  'context_info' can be empty or null, but to ensure the correct
+  decryption of the resulting ciphertext the same value must be
+  provided for decryption operation (cf. HybridDecrypt-interface).
+
+  A concrete instantiation of this interface can implement the
+  binding of 'context_info' to the ciphertext in various ways, for
+  example:
+
+  - use 'context_info' as "associated data"-input for the employed
+    AEAD symmetric encryption (cf. https://tools.ietf.org/html/rfc5116).
+  - use 'context_info' as "CtxInfo"-input for HKDF (if the implementation uses
+    HKDF as key derivation function, cf. https://tools.ietf.org/html/rfc5869).
+  """
+
+  __metaclass__ = abc.ABCMeta
+
+  @abc.abstractmethod
+  def encrypt(self, plaintext: bytes, context_info: bytes) -> bytes:
+    """Encrypts plaintext binding context_info to the resulting ciphertext."""
+    raise NotImplementedError()
diff --git a/python/hybrid/hybrid_encrypt_key_manager.py b/python/hybrid/hybrid_encrypt_key_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..a336321bd966e4f7e3dfa673da790d8399392216
--- /dev/null
+++ b/python/hybrid/hybrid_encrypt_key_manager.py
@@ -0,0 +1,47 @@
+# 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.
+
+"""Python wrapper of the CLIF-wrapped C++ Hybrid En- and Decryption key manager."""
+
+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.cc.python import hybrid_encrypt as cc_hybrid_encrypt
+from tink.python.cc.clif import cc_key_manager
+from tink.python.core import key_manager
+from tink.python.core import tink_error
+from tink.python.hybrid import hybrid_encrypt
+
+
+class _HybridEncryptCcToPyWrapper(hybrid_encrypt.HybridEncrypt):
+  """Transforms cliffed C++ HybridEncrypt primitive into a Python primitive."""
+
+  def __init__(self, cc_primitive: cc_hybrid_encrypt.HybridEncrypt):
+    self._hybrid_encrypt = cc_primitive
+
+  @tink_error.use_tink_errors
+  def encrypt(self, plaintext: bytes, context_info: bytes) -> bytes:
+    return self._hybrid_encrypt.encrypt(plaintext, context_info)
+
+
+def from_cc_registry(
+    type_url: Text) -> key_manager.KeyManager[hybrid_encrypt.HybridEncrypt]:
+  return key_manager.KeyManagerCcToPyWrapper(
+      cc_key_manager.HybridEncryptKeyManager.from_cc_registry(type_url),
+      hybrid_encrypt.HybridEncrypt, _HybridEncryptCcToPyWrapper)
diff --git a/python/hybrid/hybrid_encrypt_wrapper.py b/python/hybrid/hybrid_encrypt_wrapper.py
new file mode 100644
index 0000000000000000000000000000000000000000..99ee383a9ad3e76db0bd245f2601b8bcaa801c68
--- /dev/null
+++ b/python/hybrid/hybrid_encrypt_wrapper.py
@@ -0,0 +1,55 @@
+# Copyright 2019 Google LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""HybridEncrypt wrapper."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from typing import Type
+
+from tink.python.core import primitive_set
+from tink.python.core import primitive_wrapper
+from tink.python.hybrid import hybrid_encrypt
+
+
+class _WrappedHybridEncrypt(hybrid_encrypt.HybridEncrypt):
+  """Implements HybridEncrypt for a set of HybridEncrypt primitives."""
+
+  def __init__(self, pset: primitive_set.PrimitiveSet):
+    self._primitive_set = pset
+
+  def encrypt(self, plaintext: bytes, context_info: bytes) -> bytes:
+    primary = self._primitive_set.primary()
+    return primary.identifier + primary.primitive.encrypt(
+        plaintext, context_info)
+
+
+class HybridEncryptWrapper(
+    primitive_wrapper.PrimitiveWrapper[hybrid_encrypt.HybridEncrypt]):
+  """HybridEncryptWrapper is the PrimitiveWrapper for HybridEncrypt.
+
+  The returned primitive works with a keyset (rather than a single key). To
+  encrypt a plaintext, it uses the primary key in the keyset, and prepends to
+  the ciphertext a certain prefix associated with the primary key.
+  """
+
+  def wrap(self,
+           pset: primitive_set.PrimitiveSet) -> hybrid_encrypt.HybridEncrypt:
+    return _WrappedHybridEncrypt(pset)
+
+  def primitive_class(self) -> Type[hybrid_encrypt.HybridEncrypt]:
+    return hybrid_encrypt.HybridEncrypt
diff --git a/python/hybrid/hybrid_key_manager_test.py b/python/hybrid/hybrid_key_manager_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..a52dd8260f08e157805827257bf69977287df14c
--- /dev/null
+++ b/python/hybrid/hybrid_key_manager_test.py
@@ -0,0 +1,125 @@
+# 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.hybrid_key_manager."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+from tink.proto import common_pb2
+from tink.proto import ecies_aead_hkdf_pb2
+from tink.proto import tink_pb2
+from tink.python.aead import aead_key_templates
+from tink.python.core import tink_config
+from tink.python.core import tink_error
+from tink.python.hybrid import hybrid_decrypt
+from tink.python.hybrid import hybrid_decrypt_key_manager
+from tink.python.hybrid import hybrid_encrypt
+from tink.python.hybrid import hybrid_encrypt_key_manager
+from tink.python.hybrid import hybrid_key_templates
+
+
+def setUpModule():
+  tink_config.register()
+
+
+def _hybrid_decrypt_key_manager():
+  return hybrid_decrypt_key_manager.from_cc_registry(
+      'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey')
+
+
+def _hybrid_encrypt_key_manager():
+  return hybrid_encrypt_key_manager.from_cc_registry(
+      'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey')
+
+
+class HybridKeyManagerTest(googletest.TestCase):
+
+  def test_hybrid_decrypt_primitive_class(self):
+    self.assertEqual(_hybrid_decrypt_key_manager().primitive_class(),
+                     hybrid_decrypt.HybridDecrypt)
+
+  def test_hybrid_encrypt_primitive_class(self):
+    self.assertEqual(_hybrid_encrypt_key_manager().primitive_class(),
+                     hybrid_encrypt.HybridEncrypt)
+
+  def test_hybrid_decrypt_key_type(self):
+    self.assertEqual(
+        _hybrid_decrypt_key_manager().key_type(),
+        'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey')
+
+  def test_hybrid_encrypt_key_type(self):
+    self.assertEqual(
+        _hybrid_encrypt_key_manager().key_type(),
+        'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey')
+
+  def test_new_key_data(self):
+    key_manager = _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(tink_error.TinkError,
+                                'Unsupported elliptic curve'):
+      _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_new_key_data_on_public_key_manager_fails(self):
+    key_format = ecies_aead_hkdf_pb2.EciesAeadHkdfKeyFormat()
+    key_template = tink_pb2.KeyTemplate()
+    key_template.type_url = (
+        'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPublicKey')
+    key_template.value = key_format.SerializeToString()
+    key_template.output_prefix_type = tink_pb2.TINK
+    with self.assertRaisesRegex(tink_error.TinkError,
+                                'Operation not supported for public keys'):
+      key_manager = _hybrid_encrypt_key_manager()
+      key_manager.new_key_data(key_template)
+
+  def test_encrypt_decrypt(self):
+    decrypt_key_manager = _hybrid_decrypt_key_manager()
+    encrypt_key_manager = _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_enc = encrypt_key_manager.primitive(public_key_data)
+    ciphertext = hybrid_enc.encrypt(b'some plaintext', b'some context info')
+    hybrid_dec = decrypt_key_manager.primitive(key_data)
+    self.assertEqual(hybrid_dec.decrypt(ciphertext, b'some context info'),
+                     b'some plaintext')
+
+  def test_decrypt_fails(self):
+    decrypt_key_manager = _hybrid_decrypt_key_manager()
+    key_data = decrypt_key_manager.new_key_data(
+        hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM)
+    hybrid_dec = decrypt_key_manager.primitive(key_data)
+    with self.assertRaisesRegex(tink_error.TinkError, 'ciphertext too short'):
+      hybrid_dec.decrypt(b'bad ciphertext', b'some context info')
+
+if __name__ == '__main__':
+  googletest.main()
diff --git a/python/hybrid/hybrid_key_templates.py b/python/hybrid/hybrid_key_templates.py
new file mode 100644
index 0000000000000000000000000000000000000000..a79a92fa5d954f8dd25172bc37454373d03693f9
--- /dev/null
+++ b/python/hybrid/hybrid_key_templates.py
@@ -0,0 +1,65 @@
+# 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.
+
+"""Pre-generated KeyTemplate for HybridEncryption.
+
+One can use these templates to generate new tink_pb2.Keyset with
+tink_pb2.KeysetHandle. To generate a new keyset that contains a single
+tink_pb2.HmacKey, one can do:
+handle = keyset_handle.KeysetHandle(mac_key_templates.HMAC_SHA256_128BITTAG).
+"""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import google_type_annotations
+from __future__ import print_function
+
+from tink.proto import common_pb2
+from tink.proto import ecies_aead_hkdf_pb2
+from tink.proto import tink_pb2
+from tink.python.aead import aead_key_templates
+
+
+def create_ecies_aead_hkdf_key_template(
+    curve_type: common_pb2.EllipticCurveType,
+    ec_point_format: common_pb2.EcPointFormat,
+    hash_type: common_pb2.HashType,
+    dem_key_template: tink_pb2.KeyTemplate) -> tink_pb2.KeyTemplate:
+  """Creates a HMAC KeyTemplate, and fills in its values."""
+  key_format = ecies_aead_hkdf_pb2.EciesAeadHkdfKeyFormat()
+  key_format.params.kem_params.curve_type = curve_type
+  key_format.params.kem_params.hkdf_hash_type = hash_type
+  key_format.params.dem_params.aead_dem.CopyFrom(dem_key_template)
+  key_format.params.ec_point_format = ec_point_format
+
+  key_template = tink_pb2.KeyTemplate()
+  key_template.type_url = (
+      'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey')
+  key_template.value = key_format.SerializeToString()
+  key_template.output_prefix_type = tink_pb2.TINK
+  return key_template
+
+
+ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM = create_ecies_aead_hkdf_key_template(
+    curve_type=common_pb2.NIST_P256,
+    ec_point_format=common_pb2.UNCOMPRESSED,
+    hash_type=common_pb2.SHA256,
+    dem_key_template=aead_key_templates.AES128_GCM)
+
+ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256 = (
+    create_ecies_aead_hkdf_key_template(
+        curve_type=common_pb2.NIST_P256,
+        ec_point_format=common_pb2.UNCOMPRESSED,
+        hash_type=common_pb2.SHA256,
+        dem_key_template=aead_key_templates.AES128_CTR_HMAC_SHA256))
diff --git a/python/hybrid/hybrid_key_templates_test.py b/python/hybrid/hybrid_key_templates_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..3251698fca88b2637fe25201e925bed5585d8e13
--- /dev/null
+++ b/python/hybrid/hybrid_key_templates_test.py
@@ -0,0 +1,83 @@
+# 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.hybrid_key_templates."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+from tink.proto import common_pb2
+from tink.proto import ecies_aead_hkdf_pb2
+from tink.proto import tink_pb2
+from tink.python.aead import aead_key_templates
+from tink.python.hybrid import hybrid_key_templates
+
+
+class HybridKeyTemplatesTest(googletest.TestCase):
+
+  def test_ecies_p256_hkdf_hmac_sha256_aes128_gcm(self):
+    template = hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_GCM
+    self.assertEqual(
+        'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey',
+        template.type_url)
+    self.assertEqual(tink_pb2.TINK, template.output_prefix_type)
+    key_format = ecies_aead_hkdf_pb2.EciesAeadHkdfKeyFormat()
+    key_format.ParseFromString(template.value)
+    self.assertEqual(key_format.params.kem_params.curve_type,
+                     common_pb2.NIST_P256)
+    self.assertEqual(key_format.params.dem_params.aead_dem,
+                     aead_key_templates.AES128_GCM)
+
+  def test_ecies_p256_hkdf_hmac_sha256_aes128_ctr_hmac_sha256(self):
+    template = (
+        hybrid_key_templates.ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256)
+    self.assertEqual(
+        'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey',
+        template.type_url)
+    self.assertEqual(tink_pb2.TINK, template.output_prefix_type)
+    key_format = ecies_aead_hkdf_pb2.EciesAeadHkdfKeyFormat()
+    key_format.ParseFromString(template.value)
+    self.assertEqual(key_format.params.kem_params.curve_type,
+                     common_pb2.NIST_P256)
+    self.assertEqual(key_format.params.dem_params.aead_dem,
+                     aead_key_templates.AES128_CTR_HMAC_SHA256)
+
+  def test_create_aes_eax_key_template(self):
+    # Intentionally using 'weird' or invalid values for parameters,
+    # to test that the function correctly puts them in the resulting template.
+    template = hybrid_key_templates.create_ecies_aead_hkdf_key_template(
+        curve_type=common_pb2.NIST_P521,
+        ec_point_format=common_pb2.DO_NOT_USE_CRUNCHY_UNCOMPRESSED,
+        hash_type=common_pb2.SHA1,
+        dem_key_template=aead_key_templates.AES256_EAX)
+    self.assertEqual(
+        'type.googleapis.com/google.crypto.tink.EciesAeadHkdfPrivateKey',
+        template.type_url)
+    self.assertEqual(tink_pb2.TINK, template.output_prefix_type)
+    key_format = ecies_aead_hkdf_pb2.EciesAeadHkdfKeyFormat()
+    key_format.ParseFromString(template.value)
+    self.assertEqual(key_format.params.kem_params.curve_type,
+                     common_pb2.NIST_P521)
+    self.assertEqual(key_format.params.kem_params.hkdf_hash_type,
+                     common_pb2.SHA1)
+    self.assertEqual(key_format.params.ec_point_format,
+                     common_pb2.DO_NOT_USE_CRUNCHY_UNCOMPRESSED)
+    self.assertEqual(key_format.params.dem_params.aead_dem,
+                     aead_key_templates.AES256_EAX)
+
+
+if __name__ == '__main__':
+  googletest.main()
diff --git a/python/hybrid/hybrid_wrapper_test.py b/python/hybrid/hybrid_wrapper_test.py
new file mode 100644
index 0000000000000000000000000000000000000000..a4df8409ad8278b736830dbc8e3695a0043a761d
--- /dev/null
+++ b/python/hybrid/hybrid_wrapper_test.py
@@ -0,0 +1,171 @@
+# 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.aead_wrapper."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import unittest
+from tink.proto import tink_pb2
+from tink.python import core
+from tink.python.hybrid import hybrid_decrypt
+from tink.python.hybrid import hybrid_decrypt_wrapper
+from tink.python.hybrid import hybrid_encrypt
+from tink.python.hybrid import hybrid_encrypt_wrapper
+from tink.python.testing import helper
+
+
+def new_primitives_and_keys(key_id, output_prefix_type):
+  fake_dec_key = helper.fake_key(
+      key_material_type=tink_pb2.KeyData.ASYMMETRIC_PRIVATE,
+      key_id=key_id,
+      output_prefix_type=output_prefix_type)
+  fake_enc_key = helper.fake_key(
+      key_material_type=tink_pb2.KeyData.ASYMMETRIC_PUBLIC,
+      key_id=key_id, output_prefix_type=output_prefix_type)
+  fake_hybrid_decrypt = helper.FakeHybridDecrypt(
+      'fakeHybrid {}'.format(key_id))
+  fake_hybrid_encrypt = helper.FakeHybridEncrypt(
+      'fakeHybrid {}'.format(key_id))
+  return fake_hybrid_decrypt, fake_hybrid_encrypt, fake_dec_key, fake_enc_key
+
+
+class HybridWrapperTest(googletest.TestCase):
+
+  def test_encrypt_decrypt(self):
+    dec, enc, dec_key, enc_key = new_primitives_and_keys(1234, tink_pb2.TINK)
+    dec_pset = core.new_primitive_set(hybrid_decrypt.HybridDecrypt)
+    dec_pset.set_primary(dec_pset.add_primitive(dec, dec_key))
+    wrapped_dec = hybrid_decrypt_wrapper.HybridDecryptWrapper().wrap(dec_pset)
+
+    enc_pset = core.new_primitive_set(hybrid_encrypt.HybridEncrypt)
+    enc_pset.set_primary(enc_pset.add_primitive(enc, enc_key))
+    wrapped_enc = hybrid_encrypt_wrapper.HybridEncryptWrapper().wrap(enc_pset)
+
+    ciphertext = wrapped_enc.encrypt(b'plaintext', b'context_info')
+    self.assertEqual(
+        wrapped_dec.decrypt(ciphertext, b'context_info'), b'plaintext')
+
+  def test_encrypt_decrypt_with_key_rotation(self):
+    dec, enc, dec_key, enc_key = new_primitives_and_keys(1234, tink_pb2.TINK)
+    enc_pset = core.new_primitive_set(hybrid_encrypt.HybridEncrypt)
+    enc_pset.set_primary(enc_pset.add_primitive(enc, enc_key))
+    wrapped_enc = hybrid_encrypt_wrapper.HybridEncryptWrapper().wrap(enc_pset)
+    ciphertext = wrapped_enc.encrypt(b'plaintext', b'context_info')
+
+    new_dec, new_enc, new_dec_key, new_enc_key = new_primitives_and_keys(
+        5678, tink_pb2.TINK)
+    new_enc_pset = core.new_primitive_set(hybrid_encrypt.HybridEncrypt)
+    new_enc_pset.set_primary(new_enc_pset.add_primitive(new_enc, new_enc_key))
+    new_wrapped_enc = hybrid_encrypt_wrapper.HybridEncryptWrapper().wrap(
+        new_enc_pset)
+
+    new_dec, new_enc, new_dec_key, new_enc_key = new_primitives_and_keys(
+        5678, tink_pb2.TINK)
+    new_dec_pset = core.new_primitive_set(hybrid_decrypt.HybridDecrypt)
+    new_dec_pset.add_primitive(dec, dec_key)
+    new_dec_pset.set_primary(new_dec_pset.add_primitive(new_dec, new_dec_key))
+    new_wrapped_dec = hybrid_decrypt_wrapper.HybridDecryptWrapper().wrap(
+        new_dec_pset)
+
+    new_ciphertext = new_wrapped_enc.encrypt(b'new_plaintext',
+                                             b'new_context_info')
+    self.assertEqual(
+        new_wrapped_dec.decrypt(ciphertext, b'context_info'),
+        b'plaintext')
+    self.assertEqual(
+        new_wrapped_dec.decrypt(new_ciphertext, b'new_context_info'),
+        b'new_plaintext')
+
+  def test_encrypt_decrypt_with_key_rotation_from_raw(self):
+    raw_dec, raw_enc, raw_dec_key, raw_enc_key = new_primitives_and_keys(
+        1234, tink_pb2.RAW)
+    old_raw_ciphertext = raw_enc.encrypt(b'old_raw_ciphertext', b'context_info')
+
+    new_dec, new_enc, new_dec_key, new_enc_key = new_primitives_and_keys(
+        5678, tink_pb2.TINK)
+    enc_pset = core.new_primitive_set(hybrid_encrypt.HybridEncrypt)
+    enc_pset.add_primitive(raw_enc, raw_enc_key)
+    enc_pset.set_primary(enc_pset.add_primitive(new_enc, new_enc_key))
+    wrapped_enc = hybrid_encrypt_wrapper.HybridEncryptWrapper().wrap(
+        enc_pset)
+
+    dec_pset = core.new_primitive_set(hybrid_decrypt.HybridDecrypt)
+    dec_pset.add_primitive(raw_dec, raw_dec_key)
+    dec_pset.set_primary(dec_pset.add_primitive(new_dec, new_dec_key))
+    wrapped_dec = hybrid_decrypt_wrapper.HybridDecryptWrapper().wrap(dec_pset)
+
+    new_ciphertext = wrapped_enc.encrypt(b'new_plaintext', b'new_context_info')
+    self.assertEqual(
+        wrapped_dec.decrypt(old_raw_ciphertext, b'context_info'),
+        b'old_raw_ciphertext')
+    self.assertEqual(
+        wrapped_dec.decrypt(new_ciphertext, b'new_context_info'),
+        b'new_plaintext')
+
+  def test_encrypt_decrypt_two_raw_keys(self):
+    dec1, enc1, dec1_key, _ = new_primitives_and_keys(
+        1234, tink_pb2.RAW)
+    raw_ciphertext1 = enc1.encrypt(b'plaintext1', b'context_info1')
+    dec2, enc2, dec2_key, _ = new_primitives_and_keys(
+        1234, tink_pb2.RAW)
+    raw_ciphertext2 = enc2.encrypt(b'plaintext2', b'context_info2')
+
+    dec_pset = core.new_primitive_set(hybrid_decrypt.HybridDecrypt)
+    dec_pset.add_primitive(dec1, dec1_key)
+    dec_pset.set_primary(dec_pset.add_primitive(dec2, dec2_key))
+    wrapped_dec = hybrid_decrypt_wrapper.HybridDecryptWrapper().wrap(dec_pset)
+
+    self.assertEqual(
+        wrapped_dec.decrypt(raw_ciphertext1, b'context_info1'),
+        b'plaintext1')
+    self.assertEqual(
+        wrapped_dec.decrypt(raw_ciphertext2, b'context_info2'),
+        b'plaintext2')
+
+  def test_decrypt_unknown_ciphertext_fails(self):
+    unknown_enc = helper.FakeHybridEncrypt('unknownHybrid')
+    unknown_ciphertext = unknown_enc.encrypt(b'plaintext', b'context_info')
+
+    dec_pset = core.new_primitive_set(hybrid_decrypt.HybridDecrypt)
+
+    dec1, _, dec1_key, _ = new_primitives_and_keys(1234, tink_pb2.RAW)
+    dec2, _, dec2_key, _ = new_primitives_and_keys(5678, tink_pb2.TINK)
+    dec_pset.add_primitive(dec1, dec1_key)
+    dec_pset.set_primary(dec_pset.add_primitive(dec2, dec2_key))
+
+    wrapped_dec = hybrid_decrypt_wrapper.HybridDecryptWrapper().wrap(dec_pset)
+
+    with self.assertRaisesRegex(core.TinkError, 'Decryption failed'):
+      wrapped_dec.decrypt(unknown_ciphertext, b'context_info')
+
+  def test_decrypt_wrong_associated_data_fails(self):
+    dec, enc, dec_key, enc_key = new_primitives_and_keys(1234, tink_pb2.TINK)
+    dec_pset = core.new_primitive_set(hybrid_decrypt.HybridDecrypt)
+    dec_pset.set_primary(dec_pset.add_primitive(dec, dec_key))
+    wrapped_dec = hybrid_decrypt_wrapper.HybridDecryptWrapper().wrap(dec_pset)
+
+    enc_pset = core.new_primitive_set(hybrid_encrypt.HybridEncrypt)
+    enc_pset.set_primary(enc_pset.add_primitive(enc, enc_key))
+    wrapped_enc = hybrid_encrypt_wrapper.HybridEncryptWrapper().wrap(enc_pset)
+
+    ciphertext = wrapped_enc.encrypt(b'plaintext', b'context_info')
+    with self.assertRaisesRegex(core.TinkError, 'Decryption failed'):
+      wrapped_dec.decrypt(ciphertext, b'wrong_context_info')
+
+
+if __name__ == '__main__':
+  googletest.main()