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