Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 23176826bc3958a8fa5eaa80391eed06ef1865bc 0 parents
@hinnerk hinnerk authored
BIN  Documentation/CE-Schema.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3,226 Documentation/Diagrams.graffle
3,226 additions, 0 deletions not shown
24 LICENSE.txt
@@ -0,0 +1,24 @@
+Copyright (c) 2011, HIT Information-Control GmbH
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the HIT Information-Control GmbH nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL HIT Information-Control GmbH BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
185 README.md
@@ -0,0 +1,185 @@
+# Convergent Encryption Overview
+
+This module implements convergent encryption and generation of an id derived
+from the plaintext.
+
+
+# Requirements
+
+This module depends on the availability of either [pycryptopp][] or
+[pycrypto][] as provider of the AES-256 block cipher. This dependency must be
+resolved manually. By default it uses pycryptopp (as that seemed to be a bit
+faster in our tests) and falls back to pycrypto if the first one is not
+available.
+
+
+# Usage and API
+
+## convergent.SHA256d
+
+SHA-256 extension against length-extension-attacks as defined by Schneier and Fergusson. Basically just `sha256(sha256(data))`
+
+ >>> from convergent import SHA256d
+ >>> s = SHA256d()
+ >>> s.update("Lorem ipsum dolor sit amet")
+ >>> s.digest()
+ "\xa1\xdbyA\x04\xf5\xa6S'1\xe7\xa0\xf3\xfd9\x07y2\xa3\xb9x\xcc\x9e%\x0f %\x9d\xa9\x00\xda\xd4"
+ >>> s.hexdigest()
+ 'a1db794104f5a6532731e7a0f3fd39077932a3b978cc9e250f20259da900dad4'
+
+
+## convergent.ConvergentEncryption
+
+Convergent encryption using SHA256d and AES-256 CTR with added security and
+block id generation for deduplicated content addressable storage.
+
+Example encrypting the lorem ipsum[^1]:
+
+ >>> from convergent import ConvergentEncryption
+ >>> c1 = ConvergentEncryption("hard to guess secret")
+ >>> key, blockid, ciphertext = c1.encrypt(lorem)
+ >>> len(lorem) == len(ciphertext)
+ True
+ >>> c2 = ConvergentEncryption()
+ >>> plain_text = c2.decrypt(key, ciphertext)
+ >>> plain_text == lorem
+ True
+
+### `convergent.ConvergentEncryption(secret, warn)`
+
+`secret`: an optional secret string that guards against confirmation-of-a-file
+attack and learn-partial-information attack. The secret is **not needed** for
+successfull decryption but only to verify if the decryption process was
+successfull.
+
+`warn`: True by default, sends a warning message to the logging system if no
+secret was given. Only one log message per process is logged.
+
+### `convergent.ConvergentEncryption.set_convergence_secret(secret)`
+
+`secret`: See `secret` above. Used to set the secret if the class is used as a
+mix-in. The secret can only be set once.
+
+Returns nothing
+
+Raises convergent.CryptError if the secret was already set.
+
+
+### `convergent.ConvergentEncryption.encrypt(data)`
+
+Encrypts the string `data`.
+
+Returns a tuple of three: the encryption key (needed for decryption), a block
+id and the encrypted data.
+
+### `convergent.ConvergentEncryption.decrypt(key, ciphertext, verify=False)`
+
+Decrypts the ciphertext using `key`. If verify is true and the convergence
+secret was set the decrypted plain text is verified and convergent.CryptError
+raised if the decryption process was not successfull.
+
+
+### `convergent.encrypt_key(key, nonce, data)`
+
+Convenience function. En- or decrypts data using a one time key calculated
+from `key` and `nonce`.
+
+`Nonce` may become publicly known but must only be used once or else the
+system becomes insecure.
+
+Example:
+
+ >>> import os, convergent
+ >>> nonce = os.urandom(32).encode("hex")
+ >>> ciphertext = convergent.encrypt_key("password", nonce,
+ "this is totally secret data")
+ >>> ciphertext == "this is totally secret data"
+ False
+ >>> plain_text = convergent.encrypt_key("password", nonce, ciphertext)
+ >>> plain_text == "this is totally secret data"
+ True
+
+[^1]: without line breaks: "Lorem ipsum dolor sit amet, consectetur
+adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna
+aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
+nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
+reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
+Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
+deserunt mollit anim id est laborum."
+
+
+# Cryptographic Details
+
+## SHA256d
+
+The output of SHA256(data) (32 Bytes) are again fed into SHA256. The resulting
+32 Bytes are used as a cryptographic hash.
+
+## Convergent Encryption and deduplicated storage
+
+Convergent encryptions uses the cryptographic hash of the plaintext as the
+encryption key so that identical plaintexts always encrypt to identical
+ciphertext values as it always uses identical encryption keys.
+
+This implementation uses SHA256d as a cryptographic hash function and AES-256 in Counter (CTR) mode as a block cipher.
+
+By applying a cryptographic hash function to the encryption key a storage id
+may be constructed that when uses in an addressing schema allows the
+construction of efficiently used encrypted storage as identical blocks resolve
+to the same id.
+
+As of now (02/2011) at least two weaknesses of this encryption schema are
+known: [confirmation-of-a-file attack and learn-partial-information
+attack][attacks1]. Both can be adverted by mixing a secret value into the
+encryption key.
+
+This module works as follows, the additional secret and the merge step are
+optional:
+
+![Convergent Encryption Schema](Documentation/CE-Schema.png)
+
+Where `secret` is a random string of at least 32 Bytes and `append` is
+technically implemented by first updating an initialized SHA256d object with
+the plain text and second with the secret.
+
+
+# Changelog
+
+* 0.2 2011-02-28 Public release
+* 0.1 Initial version
+
+# LICENSE
+
+ Copyright (c) 2011, HIT Information-Control GmbH
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the HIT Information-Control GmbH nor the names of
+ its contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL HIT Information-Control GmbH BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+[attacks1]: http://www.mail-archive.com/cryptography@metzdowd.com/msg08949.html
+[pycrypto]: http://pypi.python.org/pypi/pycrypto
+[pycryptopp]: http://pypi.python.org/pypi/pycryptopp
37 convergent/__init__.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2011, HIT Information-Control GmbH
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# * Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# * Neither the name of the HIT Information-Control GmbH nor the names of its
+# contributors may be used to endorse or promote products
+# derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL HIT Information-Control GmbH BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+# OF SUCH DAMAGE.
+
+
+from crypto import SHA256d, ConvergentEncryption, encrypt_key
274 convergent/crypto.py
@@ -0,0 +1,274 @@
+# Copyright (c) 2011, HIT Information-Control GmbH
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# * Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# * Neither the name of the HIT Information-Control GmbH nor the names of its
+# contributors may be used to endorse or promote products
+# derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL HIT Information-Control GmbH BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+# OF SUCH DAMAGE.
+
+
+"""
+creates and updates crypto- and weak hashes. Operates on blocks of data.
+"""
+
+from hashlib import sha256
+from tools import clean_string
+import struct
+import logging
+
+
+class HashError(Exception):
+ pass
+
+class CryptError(HashError):
+ pass
+
+log = logging.getLogger("convergent")
+
+
+KByte = 1024
+MByte = KByte * 1024
+
+try:
+ # use either pycryptopp
+ from pycryptopp.cipher.aes import AES
+except ImportError:
+ # or use PyCrypto
+ from Crypto.Cipher import AES
+
+
+class Counter(object):
+ """ 16 Byte binary counter
+
+ Example:
+ c = Counter()
+ c() => \00 * 16
+ c() => \00...01
+ """
+
+ def __init__(self, a=0, b=0, c=0, d=0):
+ self.a = a
+ self.b = b
+ self.c = c
+ self.d = d
+
+ first = True
+ def __call__(self):
+ if self.first:
+ self.first = False
+ else:
+ if self.d < 0xFFFFFFFF:
+ self.d += 1 # increment byte 0
+ elif self.c < 0xFFFFFFFF:
+ self.c += 1 # increment byte 1
+ self.d = 0 # reset byte 0
+ elif self.b < 0xFFFFFFFF:
+ self.b += 1 # increment byte 2
+ self.c = self.d = 0 # reset bytes 0 and 1
+ elif self.a < 0xFFFFFFFF:
+ self.a += 1 # increment byte 3
+ self.b = self.c = self.d = 0 # reset bytes 0, 1, 2
+ return struct.pack(">4L", self.a, self.b, self.c, self.d)
+
+
+def aes(key, data, counter=False):
+ """ encrypt data with aes, using either pycryptopp or PyCrypto.
+ Args
+ key: The encryption key
+ data: plain text data
+ counter: a callable, usually not needed
+ """
+ # using either pycryptopp...
+ if hasattr(AES, "process"):
+ a = AES(key)
+ return a.process(data)
+ # ... or PyCrypto
+ counter = counter or Counter()
+ a = AES.new(key, AES.MODE_CTR, counter=counter)
+ rest = len(data) % 16
+ if not rest:
+ return a.encrypt(data)
+ # Data length must be a multiple of 16
+ # Pad with bytes all of the same value as the number of padding bytes
+ pad = (16 - rest)
+ data += chr(pad) * pad
+ return a.encrypt(data)[:-pad]
+
+
+class SHA256d(object):
+ """ implements SHA-265d against length-extensions-attacks
+ as defined by Schneier and Fergusson
+ """
+
+ def __init__(self, data=None, truncate_to=None):
+ """ SHA-265d against length-extensions-attacks
+ with optional truncation of the hash
+
+ Args:
+ data: Initial string, optional
+ truncate_to: length to truncate the hash to, optional
+ """
+ self.h = sha256()
+ self.truncate_to = truncate_to
+ if data:
+ self.h.update(data)
+
+ def update(self, data):
+ assert(isinstance(data, str))
+ self.h.update(data)
+
+ def digest(self):
+ if not hasattr(self,"_digest"):
+ self._digest = sha256(self.h.digest()).digest()[:self.truncate_to]
+ del self.h
+ return self._digest
+
+ def hexdigest(self):
+ return self.digest().encode('hex')
+
+
+class ConvergentEncryption(object):
+ """ provides convergent encryption and decryption
+
+ This class provides convergent en-/decryption and provides
+ a block id that can calculated from the encryption key.
+ This class can be either used stand alone or as a mix-in.
+
+ Attributes
+ info: describes the cryptographic hash and the block
+ cipher algorithms used
+ """
+
+ info = "Digest: SHA-256d, Enc-Algo: AES 256 CTR"
+ __convergence_secret = None
+
+ def __init__(self, secret=None, warn=True):
+ """ initializes a ConvergentEncryption object
+
+ Args
+ secret: string, optional, to defeat confirmation-of-a-file attack
+ warn: bool, default: True, log a warning if no secret was given
+
+ """
+ if secret:
+ self.set_convergence_secret(secret)
+ if not warn:
+ self.__warn_convergence(warn=False)
+
+ def set_convergence_secret(self, secret):
+ """ sets the secret used to defeat confirmation-of-a-file attack
+ """
+ secret = clean_string(secret)
+ if self.__convergence_secret and self.__convergence_secret != secret:
+ msg = "Do not change the convergence secret during encryption!"
+ raise CryptError(msg)
+ self.__convergence_secret = secret
+
+ @classmethod
+ def __warn_convergence(cls, warn=True):
+ """ Utter this warning only once per system run"""
+ if not hasattr(cls, "warned") and warn:
+ msg = "No convergence secret, some information may leak."
+ log.warning(msg)
+ cls.warned = True
+
+ def __sec_key(self, data):
+ """ returns secret key and block id
+
+ Args
+ data: string
+ """
+ h = SHA256d(data)
+ if not self.__convergence_secret:
+ self.__warn_convergence()
+ else:
+ h.update(self.__convergence_secret)
+ key = h.digest()
+ del h
+ id = SHA256d(key).digest()
+ return key, id
+
+ def encrypt(self, data):
+ """ encrypt data with convergence encryption.
+
+ Args
+ data: str, the plain text to be encrypted
+
+ Returns
+ key: hash(block), encryption key
+ id: hash(hash(block), block ID
+ ciphertext: enc(key, block)
+ """
+ assert(isinstance(data, str))
+ key, id = self.__sec_key(data)
+ return key, id, aes(key, data)
+
+ def decrypt(self, key, ciphertext, verify=False):
+ """ decrypt data with convergence encryption.
+
+ Args
+ key: str, encryption key
+ cipher: str, ciphertext
+ verify: bool, verify decrypted data, default: False
+
+ Returns
+ the plain text
+ """
+ plain = aes(key, ciphertext)
+ if verify:
+ h = SHA256d(plain)
+ if self.__convergence_secret:
+ h.update(self.__convergence_secret)
+ digest = h.digest()
+ # can verify only if convergence secret is known!
+ if self.__convergence_secret and not key == digest:
+ msg = "Block verification error on %s." % SHA256d(key).hexdigest()
+ log.error(msg)
+ raise CryptError(msg)
+ return plain
+
+
+def encrypt_key(key, nonce, data):
+ """ use "key" and "nonce" to generate a one time key and en-/decrypt
+ "data" with the one time key.
+
+ Args
+ key: encryption key
+ nounce: exactly once used string (try a time-based UUID)
+ data: the encrypted data
+ Returns
+ ciphertext: AES256 encrypted data
+ """
+
+ key = clean_string(key)
+ key = SHA256d(key).digest()
+ nonce_hash = SHA256d(nonce).digest()# assert 32 bytes key
+ enc_key = aes(key, nonce_hash) # generate encryption key
+ return aes(enc_key, data) # encrypt data using the new key
59 convergent/tools.py
@@ -0,0 +1,59 @@
+# Copyright (c) 2011, HIT Information-Control GmbH
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# * Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# * Neither the name of the HIT Information-Control GmbH nor the names of its
+# contributors may be used to endorse or promote products
+# derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL HIT Information-Control GmbH BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+# OF SUCH DAMAGE.
+
+
+class CleanupError(Exception):
+ pass
+
+
+
+
+def clean_string(s):
+ """ returns str from unicode, raises if not str or unicode.
+
+ >>> clean_string(u"test")
+ 'test'
+ >>> clean_string("test")
+ 'test'
+ >>> b = clean_string(u'\\xc3\\xa4\\xc3\\xb6\\xc3\\xbc')
+ >>> assert b == '\xc3\x83\xc2\xa4\xc3\x83\xc2\xb6\xc3\x83\xc2\xbc'
+ """
+ if isinstance(s, str):
+ return s
+ try:
+ return s.encode("UTF-8")
+ except:
+ raise CleanupError("Unicode or string works but not %r." % type(s))
+
66 setup.py
@@ -0,0 +1,66 @@
+# Copyright (c) 2011, HIT Information-Control GmbH
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# * Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# * Neither the name of the HIT Information-Control GmbH nor the names of its
+# contributors may be used to endorse or promote products
+# derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL HIT Information-Control GmbH BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+# OF SUCH DAMAGE.
+
+
+import os
+from setuptools import setup, find_packages
+
+
+setup(
+ name = "convergent",
+ description = "Convergent encryption library, encrypts with AES 256 CTR using the SHA256d hash of the plain text as key.",
+ long_description = open('README.md').read(),
+ version = "0.2",
+
+ install_requires = ['setuptools'],
+
+ package_dir = {'': '.'},
+ packages = find_packages(),
+ test_suite = "tests",
+
+ url = 'https://github.com/HITGmbH/py-convergent-encryption',
+ license = open("LICENSE.txt").read(),
+ author = 'Hinnerk Haardt, HIT Information-Control GmbH',
+ author_email = 'haardt@information-control.de',
+
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Security :: Cryptography',
+ ]
+)
36 tests/__init__.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2011, HIT Information-Control GmbH
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# * Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# * Neither the name of the HIT Information-Control GmbH nor the names of its
+# contributors may be used to endorse or promote products
+# derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL HIT Information-Control GmbH BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+# OF SUCH DAMAGE.
+
+import test_crypto
216 tests/test_crypto.py
@@ -0,0 +1,216 @@
+# Copyright (c) 2011, HIT Information-Control GmbH
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# * Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# * Neither the name of the HIT Information-Control GmbH nor the names of its
+# contributors may be used to endorse or promote products
+# derived from this software without specific prior written
+# permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL HIT Information-Control GmbH BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+# OF SUCH DAMAGE.
+
+
+import tempfile
+import shutil
+import os
+import hashlib
+import logging
+import unittest
+from convergent import crypto
+
+log = logging.getLogger("convergent.test_hashes")
+
+# some generic strings
+STRINGS = ("\x00", "\x01", "\xFF", "\x00"*15,
+ "\x00\xFF"*20, "test"*1024, "\xFF"*23)
+
+
+
+class SHA256dHashTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.hex = "f5a1f608f4cd6abaf52e716739a68bc83b0e91872c1f70916e59756ea122f047"
+
+ def test_sha256d_wo_initial_data(self):
+ h = crypto.SHA256d()
+ h.update("test test 123")
+ h.update("test test 345")
+ self.assertEqual(h.hexdigest(), self.hex)
+
+ def test_sha256d_with_initial_data(self):
+ h = crypto.SHA256d("test test 123")
+ h.update("test test 345")
+ self.assertEqual(h.hexdigest(), self.hex)
+
+ def test_sha256d_cache(self):
+ h = crypto.SHA256d("test test 123test test 345")
+ void = h.digest() #@UnusedVariable
+ self.assertEqual(h.hexdigest(), self.hex)
+
+
+class CounterTestCase(unittest.TestCase):
+
+ def test_Counter(self):
+ c = crypto.Counter()
+ for x in range(300):
+ c()
+ self.assertEqual(c(), "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01,")
+
+
+
+class AESTestCase(unittest.TestCase):
+
+ #
+ # NIST TESTVEKTOREN AES256CTR (SP800-38A)
+ #
+ key = "\x60\x3d\xeb\x10\x15\xca\x71\xbe\x2b\x73\xae\xf0\x85\x7d\x77\x81\x1f\x35\x2c\x07\x3b\x61\x08\xd7\x2d\x98\x10\xa3\x09\x14\xdf\xf4"
+ c = (0xf0f1f2f3, 0xf4f5f6f7, 0xf8f9fafb, 0xfcfdfeff)
+ plain = ("\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96\xe9\x3d\x7e\x11\x73\x93\x17\x2a"
+ "\xae\x2d\x8a\x57\x1e\x03\xac\x9c\x9e\xb7\x6f\xac\x45\xaf\x8e\x51"
+ "\x30\xc8\x1c\x46\xa3\x5c\xe4\x11\xe5\xfb\xc1\x19\x1a\x0a\x52\xef"
+ "\xf6\x9f\x24\x45\xdf\x4f\x9b\x17\xad\x2b\x41\x7b\xe6\x6c\x37\x10")
+ cipher = ("`\x1e\xc3\x13wW\x89\xa5\xb7\xa7\xf5\x04\xbb\xf3\xd2(\xf4C\xe3"
+ "\xcaMb\xb5\x9a\xca\x84\xe9\x90\xca\xca\xf5\xc5+\t0\xda\xa2="
+ "\xe9L\xe8p\x17\xba-\x84\x98\x8d\xdf\xc9\xc5\x8d\xb6z\xad\xa6"
+ "\x13\xc2\xdd\x08EyA\xa6")
+ def test_1_nist_crypto(self):
+ """ 1.: testing PyCrypto, using the NIST test vectors."""
+ # test using Crypto.Cipher.AES
+ counter = crypto.Counter(*self.c)
+ from Crypto.Cipher import AES as aes_cc
+ crypto.AES = aes_cc
+ self.assertEquals(self.cipher, crypto.aes(self.key, self.plain,
+ counter=counter))
+ #
+ # we can't test using pycryptopp.cipher.aes
+ # since the counter of PyCryptoPP can't be set
+ #
+
+ def test_2_self_cryptopp(self):
+ """ 2.: testing CryptoPP using PyCrypto..."""
+ # generate a cipher string using nist plaintext BUT counter start at "\x00"*16
+ # Crypto.Cipher.AES shoud work okay, since tested with NIST test vectors
+ from Crypto.Cipher import AES as aes_cc
+ crypto.AES = aes_cc
+ counter = crypto.Counter()
+ cipher0 = crypto.aes(self.key, self.plain, counter=counter)
+ # testing PyCryptoPP, not as well as Crypto.Cipher.AES
+ # but well... better that not at all I guess
+ from pycryptopp.cipher.aes import AES as aes_pp #@UnresolvedImport
+ crypto.AES = aes_pp
+ self.assertEquals(cipher0, crypto.aes(self.key, self.plain))
+
+ def test_3_padding_and_compatibility(self):
+ """ 3.: en- and decryption using CryptoPP and PyCrypto with """
+ strings = STRINGS
+
+ from pycryptopp.cipher.aes import AES as aes_pp #@UnresolvedImport
+ from Crypto.Cipher import AES as aes_cc
+ for s in strings:
+ # testing encryption
+ #
+ crypto.AES = aes_cc # monkey-patch Crypto.Cipher.AES
+ counter = crypto.Counter()
+ cipher0 = crypto.aes(self.key, s, counter=counter)
+ # using pycryptopp.cipher.aes
+ crypto.AES = aes_pp # monkey-patch pycryptopp.cipher.aes.AES
+ cipher1 = crypto.aes(self.key, s)
+ self.assertEquals(cipher0, cipher1)
+ #
+ # testing decryption
+ plain1 = crypto.aes(self.key, cipher0) # still using pycryptopp.cipher.aes.AES
+ crypto.AES = aes_cc # monkey-patch Crypto.Cipher.AES
+ counter = crypto.Counter()
+ plain0 = crypto.aes(self.key, cipher0, counter=counter)
+ self.assertEquals(plain0, plain1)
+
+
+class ConvergentEncryptionTestBase(crypto.ConvergentEncryption):
+
+ def test_set_convergence_secret(self):
+ c1 = self.encrypt("test123")
+ self.set_convergence_secret("B"*5)
+ c2 = self.encrypt("test123")
+ self.assertNotEqual(c1, c2)
+
+ def test_encrypt_decrypt(self):
+ for data in self.strings:
+ skey, pkey, crypted = self.encrypt(data)
+ self.assertNotEquals(data, crypted)
+ plain = self.decrypt(skey, crypted, verify=True)
+ self.assertEqual(data, plain)
+
+ def test_encrypt_error(self):
+ for data in self.strangelings:
+ self.assertRaises(AssertionError, self.encrypt, data)
+
+ def test_encrypt_decrypt_with_convergence(self):
+ for plaintext in self.strings:
+ self.set_convergence_secret("B"*5)
+ skey, pkey, crypted = self.encrypt(plaintext)
+ self.assertNotEquals(plaintext, crypted)
+ decrypted = self.decrypt(skey, crypted, verify=True)
+ self.assertEqual(plaintext, decrypted)
+
+ def test_convergence(self):
+ without_convergence = [] # (key, cyphertext), ...
+ for plaintext in self.strings:
+ skey, pkey, crypted = self.encrypt(plaintext)
+ without_convergence.append((skey, crypted))
+ self.set_convergence_secret("B"*5)
+ with_convergence = [] # (key, cyphertext), ...
+ for plaintext in self.strings:
+ skey, pkey, crypted = self.encrypt(plaintext)
+ with_convergence.append((skey, crypted))
+ for w, wo in zip(with_convergence, without_convergence):
+ self.assertTrue(w[0] != wo[0]) # key must not be equal
+ self.assertTrue(w[1] != wo[1]) # cyphertext must not be equal
+
+ def test_process_key(self):
+ cyphertext = crypto.encrypt_key("test123", "nounce", "my_sec.key")
+ plaintext = crypto.encrypt_key("test123", "nounce", cyphertext)
+ self.assertEqual(plaintext, "my_sec.key")
+
+
+
+from pycryptopp.cipher.aes import AES as aes_pp
+crypto.AES = aes_pp
+class ConvergentEncryptionPyCryptoPPTestCase(unittest.TestCase, ConvergentEncryptionTestBase):
+ """ConvergentEncryption TestCase using pycryptopp."""
+ def setUp(self):
+ self.strings = STRINGS
+ self.strangelings = (1, 0x12, False)
+
+from Crypto.Cipher import AES as aes_cc
+crypto.AES = aes_cc
+class ConvergentEncryptionPyCryptoTestCase(unittest.TestCase, ConvergentEncryptionTestBase):
+ """ConvergentEncryption TestCase using PyCrypto"""
+ def setUp(self):
+ self.strings = STRINGS
+ self.strangelings = (1, 0x12, False)
+
+

0 comments on commit 2317682

Please sign in to comment.
Something went wrong with that request. Please try again.