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()