crypto.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. # Copyright (c) 2011, HIT Information-Control GmbH
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or
  5. # without modification, are permitted provided that the following
  6. # conditions are met:
  7. #
  8. # * Redistributions of source code must retain the above
  9. # copyright notice, this list of conditions and the following
  10. # disclaimer.
  11. #
  12. # * Redistributions in binary form must reproduce the above
  13. # copyright notice, this list of conditions and the following
  14. # disclaimer in the documentation and/or other materials
  15. # provided with the distribution.
  16. #
  17. # * Neither the name of the HIT Information-Control GmbH nor the names of its
  18. # contributors may be used to endorse or promote products
  19. # derived from this software without specific prior written
  20. # permission.
  21. #
  22. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  23. # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  24. # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  25. # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  26. # DISCLAIMED. IN NO EVENT SHALL HIT Information-Control GmbH BE
  27. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
  28. # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  29. # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  30. # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  31. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  32. # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  33. # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
  34. # OF SUCH DAMAGE.
  35. """
  36. creates and updates crypto- and weak hashes. Operates on blocks of data.
  37. """
  38. from hashlib import sha256
  39. from tools import clean_string
  40. import struct
  41. import logging
  42. class HashError(Exception):
  43. pass
  44. class CryptError(HashError):
  45. pass
  46. log = logging.getLogger("convergent")
  47. KByte = 1024
  48. MByte = KByte * 1024
  49. try:
  50. # use either pycryptopp
  51. from pycryptopp.cipher.aes import AES
  52. except ImportError:
  53. # or use PyCrypto
  54. from Crypto.Cipher import AES
  55. class Counter(object):
  56. """ 16 Byte binary counter
  57. Example:
  58. c = Counter()
  59. c() => \00 * 16
  60. c() => \00...01
  61. """
  62. def __init__(self, a=0, b=0, c=0, d=0):
  63. self.a = a
  64. self.b = b
  65. self.c = c
  66. self.d = d
  67. first = True
  68. def __call__(self):
  69. if self.first:
  70. self.first = False
  71. else:
  72. if self.d < 0xFFFFFFFF:
  73. self.d += 1 # increment byte 0
  74. elif self.c < 0xFFFFFFFF:
  75. self.c += 1 # increment byte 1
  76. self.d = 0 # reset byte 0
  77. elif self.b < 0xFFFFFFFF:
  78. self.b += 1 # increment byte 2
  79. self.c = self.d = 0 # reset bytes 0 and 1
  80. elif self.a < 0xFFFFFFFF:
  81. self.a += 1 # increment byte 3
  82. self.b = self.c = self.d = 0 # reset bytes 0, 1, 2
  83. return struct.pack(">4L", self.a, self.b, self.c, self.d)
  84. def aes(key, data, counter=False):
  85. """ encrypt data with aes, using either pycryptopp or PyCrypto.
  86. Args
  87. key: The encryption key
  88. data: plain text data
  89. counter: a callable, usually not needed
  90. """
  91. # using either pycryptopp...
  92. if hasattr(AES, "process"):
  93. a = AES(key)
  94. return a.process(data)
  95. # ... or PyCrypto
  96. counter = counter or Counter()
  97. a = AES.new(key, AES.MODE_CTR, counter=counter)
  98. rest = len(data) % 16
  99. if not rest:
  100. return a.encrypt(data)
  101. # Data length must be a multiple of 16
  102. # Pad with bytes all of the same value as the number of padding bytes
  103. pad = (16 - rest)
  104. data += chr(pad) * pad
  105. return a.encrypt(data)[:-pad]
  106. class SHA256d(object):
  107. """ implements SHA-265d against length-extensions-attacks
  108. as defined by Schneier and Fergusson
  109. """
  110. def __init__(self, data=None, truncate_to=None):
  111. """ SHA-265d against length-extensions-attacks
  112. with optional truncation of the hash
  113. Args:
  114. data: Initial string, optional
  115. truncate_to: length to truncate the hash to, optional
  116. """
  117. self.h = sha256()
  118. self.truncate_to = truncate_to
  119. if data:
  120. self.h.update(data)
  121. def update(self, data):
  122. assert(isinstance(data, str))
  123. self.h.update(data)
  124. def digest(self):
  125. return sha256(self.h.digest()).digest()[:self.truncate_to]
  126. def hexdigest(self):
  127. return self.digest().encode('hex')
  128. class ConvergentEncryption(object):
  129. """ provides convergent encryption and decryption
  130. This class provides convergent en-/decryption and provides
  131. a block id that can calculated from the encryption key.
  132. This class can be either used stand alone or as a mix-in.
  133. Attributes
  134. info: describes the cryptographic hash and the block
  135. cipher algorithms used
  136. """
  137. info = "Digest: SHA-256d, Enc-Algo: AES 256 CTR"
  138. __convergence_secret = None
  139. def __init__(self, secret=None, warn=True):
  140. """ initializes a ConvergentEncryption object
  141. Args
  142. secret: string, optional, to defeat confirmation-of-a-file attack
  143. warn: bool, default: True, log a warning if no secret was given
  144. """
  145. if secret:
  146. self.set_convergence_secret(secret)
  147. if not warn:
  148. self.__warn_convergence(warn=False)
  149. def set_convergence_secret(self, secret):
  150. """ sets the secret used to defeat confirmation-of-a-file attack
  151. """
  152. secret = clean_string(secret)
  153. if self.__convergence_secret and self.__convergence_secret != secret:
  154. msg = "Do not change the convergence secret during encryption!"
  155. raise CryptError(msg)
  156. self.__convergence_secret = secret
  157. @classmethod
  158. def __warn_convergence(cls, warn=True):
  159. """ Utter this warning only once per system run"""
  160. if not hasattr(cls, "warned") and warn:
  161. msg = "No convergence secret, some information may leak."
  162. log.warning(msg)
  163. cls.warned = True
  164. def __sec_key(self, data):
  165. """ returns secret key and block id
  166. Args
  167. data: string
  168. """
  169. h = SHA256d(data)
  170. if not self.__convergence_secret:
  171. self.__warn_convergence()
  172. else:
  173. h.update(self.__convergence_secret)
  174. key = h.digest()
  175. del h
  176. id = SHA256d(key).digest()
  177. return key, id
  178. def encrypt(self, data):
  179. """ encrypt data with convergence encryption.
  180. Args
  181. data: str, the plain text to be encrypted
  182. Returns
  183. key: hash(block), encryption key
  184. id: hash(hash(block), block ID
  185. ciphertext: enc(key, block)
  186. """
  187. assert(isinstance(data, str))
  188. key, id = self.__sec_key(data)
  189. return key, id, aes(key, data)
  190. def decrypt(self, key, ciphertext, verify=False):
  191. """ decrypt data with convergence encryption.
  192. Args
  193. key: str, encryption key
  194. cipher: str, ciphertext
  195. verify: bool, verify decrypted data, default: False
  196. Returns
  197. the plain text
  198. """
  199. plain = aes(key, ciphertext)
  200. if verify:
  201. h = SHA256d(plain)
  202. if self.__convergence_secret:
  203. h.update(self.__convergence_secret)
  204. digest = h.digest()
  205. # can verify only if convergence secret is known!
  206. if self.__convergence_secret and not key == digest:
  207. msg = "Block verification error on %s." % SHA256d(key).hexdigest()
  208. log.error(msg)
  209. raise CryptError(msg)
  210. return plain
  211. def encrypt_key(key, nonce, data):
  212. """ use "key" and "nonce" to generate a one time key and en-/decrypt
  213. "data" with the one time key.
  214. Args
  215. key: encryption key
  216. nounce: exactly once used string (try a time-based UUID)
  217. data: the encrypted data
  218. Returns
  219. ciphertext: AES256 encrypted data
  220. """
  221. key = clean_string(key)
  222. key = SHA256d(key).digest()
  223. nonce_hash = SHA256d(nonce).digest()# assert 32 bytes key
  224. enc_key = aes(key, nonce_hash) # generate encryption key
  225. return aes(enc_key, data) # encrypt data using the new key