Source code for seccs.crypto_wrapper

"""Crypto wrapper implementations.

A crypto wrapper encapsulates all cryptographic operations that have to be
applied to node representations before they can be safely inserted into the
(untrusted) database, e.g., encryption and authentication. It defines the keys
(digests) under which (wrapped) node representations are stored and specifies
how node representations can be extracted from (digest, value) pairs stored in
the database.

This module includes several crypto wrapper implementations with different
security properties.
"""
import abc
import hashlib
import hmac
import struct
import logging


# format strings to encode parameters
FORMAT_HEIGHT = 'i'
FORMAT_ISROOT = '?'
FORMAT_HEIGHT_ISROOT = FORMAT_HEIGHT + FORMAT_ISROOT


[docs]class IntegrityError(ValueError): """Raised if an integrity verification fails.""" pass
[docs]class AuthenticityError(IntegrityError): """Raised if an authenticity verification fails.""" pass
[docs]class BaseCryptoWrapper(object): """Abstract class specifying the crypto wrapper interface.""" __metaclass__ = abc.ABCMeta __slots__ = [] """Defines the constant size of digests generated by this crypto wrapper.""" DIGEST_SIZE = None
[docs] def wrap_value(self, value, height, is_root): """Converts a node representation into a (digest, value) pair that can be safely inserted into the database. Args: value (str): The node representation. height (int): The height of the node in its chunk tree. is_root (bool): Whether or not the node is the chunk tree's root. Returns: tuple: (wrapped_value, digest). """ raise NotImplementedError
[docs] def unwrap_value(self, value, digest, height, is_root, length=-1): """Converts the representation of a node as stored in the database (e.g., an encrypted representation) into its `normal`, e.g., decrypted, representation. Args: value (str): Wrapped node representation. digest (str): Key under which the node is stored in the database. height (int): Height of the node in its chunk tree. is_root (bool): Whether or not the node is the chunk tree's root. length [Optional(int)]: Length of the content represented by this node. Defaults to -1. Returns: str: Node representation. """ raise NotImplementedError
HASHLIB_SHA256 = hashlib.sha256
[docs]class SHA_256(BaseCryptoWrapper): """SHA-256 crypto wrapper. Provides integrity for chunk tree nodes. Nodes are represented as follows: * value: <value> * digest: SHA-256(<value>) """ __slots__ = [] DIGEST_SIZE = 32 def __init__(self): super(SHA_256, self).__init__()
[docs] def wrap_value(self, value, height, is_root): """Uses SHA-256 hash of node representation as digest and value as is. See :meth:`.BaseCryptoWrapper.wrap_value`. """ m = HASHLIB_SHA256() m.update(struct.pack(FORMAT_HEIGHT, height)) m.update(value) return value, m.digest()
[docs] def unwrap_value(self, value, digest, height, is_root, length=-1): """Verifies SHA-256 hash and returns node representation on success. Raises: IntegrityError: If digest does not match. See :meth:`.BaseCryptoWrapper.unwrap_value`. """ m = HASHLIB_SHA256() m.update(struct.pack(FORMAT_HEIGHT, height)) m.update(value) if m.digest() != digest or (height == 0 and len(value) < length): raise IntegrityError return value
HMAC_new = hmac.new
[docs]class HMAC_SHA_256(BaseCryptoWrapper): """HMAC-SHA-256 crypto wrapper. Provides authenticity for chunk tree nodes based on a symmetric 32-bytes key specified during instantiation. Root nodes are handled identically to inner nodes at the same level. Nodes are represented as follows: * value: <value> * digest: HMAC-SHA-256(<key>, <height> || <value>) Args: key (str): Cryptographic key used for symmetric authentication. """ __slots__ = ['_key'] DIGEST_SIZE = 32 def __init__(self, key): super(HMAC_SHA_256, self).__init__() self._key = key
[docs] def wrap_value(self, value, height, is_root): """Uses SHA-256-based HMAC of node representation and height as digest and value as is. See :meth:`.BaseCryptoWrapper.wrap_value`. """ m = HMAC_new(self._key, digestmod=HASHLIB_SHA256) m.update(struct.pack(FORMAT_HEIGHT, height)) m.update(value) return value, m.digest()
[docs] def unwrap_value(self, value, digest, height, is_root, length=-1): """Verifies SHA-256-based HMAC and returns node representation on success. Raises: AuthenticityError: If digest does not match. See :meth:`.BaseCryptoWrapper.unwrap_value`. """ m = HMAC_new(self._key, digestmod=HASHLIB_SHA256) m.update(struct.pack(FORMAT_HEIGHT, height)) m.update(value) if m.digest() != digest or (height == 0 and len(value) < length): raise AuthenticityError return value
[docs]class HMAC_SHA_256_DISTINGUISHED_ROOT(HMAC_SHA_256): """HMAC-SHA-256 crypto wrapper with distinguished root representation. Provides authenticity for chunk tree nodes based on a symmetric 32-bytes key specified during instantiation. Root nodes are handled differently from inner nodes at the same level. Nodes are represented as follows: * value: <value> * digest: HMAC-SHA-256(<key>, <height> || <is_root> || <value>) Args: key (str): Cryptographic key used for symmetric authentication. """ __slots__ = [] def __init__(self, key): HMAC_SHA_256.__init__(self, key)
[docs] def wrap_value(self, value, height, is_root): """Uses SHA-256-based HMAC of node representation, height and is_root flag as digest and value as is. See :meth:`.BaseCryptoWrapper.wrap_value`. """ m = HMAC_new(self._key, digestmod=HASHLIB_SHA256) m.update(struct.pack(FORMAT_HEIGHT_ISROOT, height, is_root)) m.update(value) return value, m.digest()
[docs] def unwrap_value(self, value, digest, height, is_root, length=-1): """Verifies SHA-256-based HMAC and returns node representation on success. Raises: AuthenticityError: If digest does not match. See :meth:`.BaseCryptoWrapper.unwrap_value`. """ m = HMAC_new(self._key, digestmod=HASHLIB_SHA256) m.update(struct.pack(FORMAT_HEIGHT_ISROOT, height, is_root)) m.update(value) if m.digest() != digest or (height == 0 and len(value) < length): raise AuthenticityError return value
# HMAC_SHA_256_R = HMAC_SHA_256.DIGEST_SIZE + 8 # # # class HMAC_SHA_256_DISTINGUISHED_ROOT_WITH_LEAF_PADDING(HMAC_SHA_256): # # """HMAC-SHA-256 crypto wrapper with distinguished root representation and # leaf padding. # # Provides authenticity for chunk tree nodes based on a symmetric 32-bytes key # specified during instantiation. # # Root nodes are handled differently from inner nodes at the same level. # # WARNING: not yet supported by SecCS! # # Nodes are represented as follows: # * value: <value> # * digest: HMAC-SHA-256(<key>, <height> || <is_root> || <value>) # # Args: # key (str): Cryptographic key used for symmetric authentication. # """ # # __slots__ = [] # # def __init__(self, key): # HMAC_SHA_256.__init__(self, key) # # def wrap_value(self, value, height, is_root): # """Pads leaf node representations with zero bytes to make them # indifferentiable from superchunks and uses SHA-256-based HMAC of the # resulting node representation, height and is_root flag as digest. # # See :meth:`.BaseCryptoWrapper.wrap_value`. # """ # m = HMAC_new(self._key, digestmod=HASHLIB_SHA256) # m.update(struct.pack(FORMAT_HEIGHT_ISROOT, height, is_root)) # if height == 0: # value += b'\x00' * (HMAC_SHA_256_R - (len(value) % HMAC_SHA_256_R)) # m.update(value) # return value, m.digest() # # def unwrap_value(self, value, digest, height, is_root, length=-1): # """Verifies SHA-256-based HMAC, removes padding and returns resulting # node representation on success. # # Raises: # AuthenticityError: If digest does not match. # # See :meth:`.BaseCryptoWrapper.unwrap_value`. # """ # m = HMAC_new(self._key, digestmod=HASHLIB_SHA256) # m.update(struct.pack(FORMAT_HEIGHT_ISROOT, height, is_root)) # m.update(value) # if m.digest() != digest or (height == 0 and len(value) < length): # raise AuthenticityError # return value[:length] if height == 0 and length > -1 else value try: from Crypto.Cipher import AES AES_MODE_SIV = AES.MODE_SIV except ImportError: logging.getLogger(__name__).warn('PyCrypto is not available, disabling AES wrappers.') except AttributeError: logging.getLogger(__name__).warn('Your PyCrypto version is too old, disabling ' 'AES-SIV wrappers which depend on PyCrypto >= 2.7a1.') else: AES_new_fn = AES.new
[docs] class AES_SIV_256(BaseCryptoWrapper): """AES-SIV-256 crypto wrapper. Provides confidentiality and authenticity for chunk tree nodes based on a symmetric 32-bytes key specified during instantiation. Root nodes are handled identically to inner nodes at the same level. Nodes are represented as follows: * value: AES-SIV-256(<key>, <value>, additional_data=<height>) * digest: <digest produced by AES-SIV-256> Args: key (str): Cryptographic key used for symmetric encryption and authentication. Note: Requires PyCrypto >= 2.7a1. """ __slots__ = ['_key', '_zero_digest'] DIGEST_SIZE = 16 def __init__(self, key): super(AES_SIV_256, self).__init__() self._key = key """The AES-SIV implementation does not support encryption of empty strings, so we represent empty-string nodes by a designated zero_digest instead. """ cipher = AES_new_fn(self._key, AES_MODE_SIV) cipher.update(b'empty_additional_data') _, self._zero_digest = cipher.encrypt_and_digest(b'empty_string') self._zero_digest = b'\x00' * AES_SIV_256.DIGEST_SIZE
[docs] def wrap_value(self, value, height, is_root): """Encrypts node representation using deterministic authenticated encryption, i.e., with AES in SIV mode, including node height as additional data that is authenticated, resulting in a digest (MAC) that is used as `digest` and a ciphertext that is used as `value`. Note: As AES-SIV cannot encrypt empty contents, a distinguished zero digest is artifically assigned to empty node representations instead. See :meth:`.BaseCryptoWrapper.wrap_value`. """ if value == b'': return value, self._zero_digest cipher = AES_new_fn(self._key, AES_MODE_SIV) cipher.update(struct.pack(FORMAT_HEIGHT, height)) return cipher.encrypt_and_digest(value)
[docs] def unwrap_value(self, value, digest, height, is_root, length=-1): """Decrypts and verifies node representation and returns the result on success. Raises: AuthenticityError: If digest does not match. See :meth:`.BaseCryptoWrapper.unwrap_value`. """ if value == b'' and digest == self._zero_digest: return value cipher = AES_new_fn(self._key, AES_MODE_SIV) cipher.update(struct.pack(FORMAT_HEIGHT, height)) try: return cipher.decrypt_and_verify(value, digest) except ValueError as e: raise AuthenticityError(e)
[docs] class AES_SIV_256_DISTINGUISHED_ROOT(AES_SIV_256): """AES-SIV-256 crypto wrapper with distinguished root representation. Provides confidentiality and authenticity for chunk tree nodes based on a symmetric 32-bytes key specified during instantiation. Root nodes are handled differently from inner nodes at the same level. Nodes are represented as follows: * value: AES-SIV-256(<key>, <value>, additional_data=<height>||<is_root>) * digest: <digest produced by AES-SIV-256> Args: key (str): Cryptographic key used for symmetric encryption and authentication. Note: Requires PyCrypto >= 2.7a1. """ __slots__ = [] def __init__(self, key): AES_SIV_256.__init__(self, key)
[docs] def wrap_value(self, value, height, is_root): """Encrypts node representation using deterministic authenticated encryption, i.e., with AES in SIV mode, including node height and is_root flag as additional data that is authenticated, resulting in a digest (MAC) that is used as `digest` and a ciphertext that is used as `value`. See :meth:`.AES_SIV_256.wrap_value`. """ if value == b'': return value, self._zero_digest cipher = AES_new_fn(self._key, AES_MODE_SIV) cipher.update(struct.pack(FORMAT_HEIGHT_ISROOT, height, is_root)) return cipher.encrypt_and_digest(value)
[docs] def unwrap_value(self, value, digest, height, is_root, length=-1): """Decrypts and verifies node representation and returns the result on success. Raises: AuthenticityError: If digest does not match. See :meth:`.BaseCryptoWrapper.unwrap_value`. """ if value == b'' and digest == self._zero_digest: return value cipher = AES_new_fn(self._key, AES_MODE_SIV) cipher.update(struct.pack(FORMAT_HEIGHT_ISROOT, height, is_root)) try: return cipher.decrypt_and_verify(value, digest) except ValueError as e: raise AuthenticityError(e)