key.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. from __future__ import with_statement
  2. from getpass import getpass
  3. import hashlib
  4. import os
  5. import msgpack
  6. import zlib
  7. from Crypto.Cipher import AES
  8. from Crypto.Hash import SHA256, HMAC
  9. from Crypto.Util import Counter
  10. from Crypto.Util.number import bytes_to_long, long_to_bytes
  11. from Crypto.Random import get_random_bytes
  12. from Crypto.Protocol.KDF import PBKDF2
  13. from .helpers import IntegrityError, get_keys_dir
  14. PREFIX = '\0' * 8
  15. def SHA256_PDF(p, s):
  16. return HMAC.new(p, s, SHA256).digest()
  17. class Key(object):
  18. FILE_ID = 'DARC KEY'
  19. def __init__(self, store=None, password=None):
  20. if store:
  21. self.open(self.find_key_file(store), password=password)
  22. def find_key_file(self, store):
  23. id = store.id.encode('hex')
  24. keys_dir = get_keys_dir()
  25. for name in os.listdir(keys_dir):
  26. filename = os.path.join(keys_dir, name)
  27. with open(filename, 'rb') as fd:
  28. line = fd.readline().strip()
  29. if line and line.startswith(self.FILE_ID) and line[9:] == id:
  30. return filename
  31. raise Exception('Key file for store with ID %s not found' % id)
  32. def open(self, filename, prompt=None, password=None):
  33. prompt = prompt or 'Enter password for %s: ' % filename
  34. with open(filename, 'rb') as fd:
  35. lines = fd.readlines()
  36. if not lines[0].startswith(self.FILE_ID) != self.FILE_ID:
  37. raise ValueError('Not a DARC key file')
  38. self.store_id = lines[0][len(self.FILE_ID):].strip().decode('hex')
  39. cdata = (''.join(lines[1:])).decode('base64')
  40. self.password = password or ''
  41. data = self.decrypt_key_file(cdata, self.password)
  42. while not data:
  43. self.password = getpass(prompt)
  44. if not self.password:
  45. raise Exception('Key decryption failed')
  46. data = self.decrypt_key_file(cdata, self.password)
  47. if not data:
  48. print 'Incorrect password'
  49. key = msgpack.unpackb(data)
  50. if key['version'] != 1:
  51. raise IntegrityError('Invalid key file header')
  52. self.store_id = key['store_id']
  53. self.enc_key = key['enc_key']
  54. self.enc_hmac_key = key['enc_hmac_key']
  55. self.id_key = key['id_key']
  56. self.chunk_seed = key['chunk_seed']
  57. self.counter = Counter.new(64, initial_value=1, prefix=PREFIX)
  58. self.path = filename
  59. def post_manifest_load(self, config):
  60. iv = bytes_to_long(config['aes_counter']) + 100
  61. self.counter = Counter.new(64, initial_value=iv, prefix=PREFIX)
  62. def pre_manifest_write(self, manifest):
  63. manifest.config['aes_counter'] = long_to_bytes(self.counter.next_value(), 8)
  64. def encrypt_key_file(self, data, password):
  65. salt = get_random_bytes(32)
  66. iterations = 10000
  67. key = PBKDF2(password, salt, 32, iterations, SHA256_PDF)
  68. hash = HMAC.new(key, data, SHA256).digest()
  69. cdata = AES.new(key, AES.MODE_CTR, counter=Counter.new(128)).encrypt(data)
  70. d = {
  71. 'version': 1,
  72. 'salt': salt,
  73. 'iterations': iterations,
  74. 'algorithm': 'SHA256',
  75. 'hash': hash,
  76. 'data': cdata,
  77. }
  78. return msgpack.packb(d)
  79. def decrypt_key_file(self, data, password):
  80. d = msgpack.unpackb(data)
  81. assert d['version'] == 1
  82. assert d['algorithm'] == 'SHA256'
  83. key = PBKDF2(password, d['salt'], 32, d['iterations'], SHA256_PDF)
  84. data = AES.new(key, AES.MODE_CTR, counter=Counter.new(128)).decrypt(d['data'])
  85. if HMAC.new(key, data, SHA256).digest() != d['hash']:
  86. return None
  87. return data
  88. def save(self, path, password):
  89. key = {
  90. 'version': 1,
  91. 'store_id': self.store_id,
  92. 'enc_key': self.enc_key,
  93. 'enc_hmac_key': self.enc_hmac_key,
  94. 'id_key': self.enc_key,
  95. 'chunk_seed': self.chunk_seed,
  96. }
  97. data = self.encrypt_key_file(msgpack.packb(key), password)
  98. with open(path, 'wb') as fd:
  99. fd.write('%s %s\n' % (self.FILE_ID, self.store_id.encode('hex')))
  100. fd.write(data.encode('base64'))
  101. self.path = path
  102. def chpasswd(self):
  103. password, password2 = 1, 2
  104. while password != password2:
  105. password = getpass('New password: ')
  106. password2 = getpass('New password again: ')
  107. if password != password2:
  108. print 'Passwords do not match'
  109. self.save(self.path, password)
  110. return 0
  111. @staticmethod
  112. def create(store, filename, password=None):
  113. i = 1
  114. path = filename
  115. while os.path.exists(path):
  116. i += 1
  117. path = filename + '.%d' % i
  118. if password is not None:
  119. password2 = password
  120. else:
  121. password, password2 = 1, 2
  122. while password != password2:
  123. password = getpass('Key file password (Leave blank for no password): ')
  124. password2 = getpass('Key file password again: ')
  125. if password != password2:
  126. print 'Passwords do not match'
  127. key = Key()
  128. key.store_id = store.id
  129. # Chunk AES256 encryption key
  130. key.enc_key = get_random_bytes(32)
  131. # Chunk encryption HMAC key
  132. key.enc_hmac_key = get_random_bytes(32)
  133. # Chunk id HMAC key
  134. key.id_key = get_random_bytes(32)
  135. # Chunkifier seed
  136. key.chunk_seed = bytes_to_long(get_random_bytes(4))
  137. # Convert to signed int32
  138. if key.chunk_seed & 0x80000000:
  139. key.chunk_seed = key.chunk_seed - 0xffffffff - 1
  140. key.save(path, password)
  141. return Key(store, password=password)
  142. def id_hash(self, data):
  143. """Return HMAC hash using the "id" HMAC key
  144. """
  145. return HMAC.new(self.id_key, data, SHA256).digest()
  146. def encrypt(self, data):
  147. data = zlib.compress(data)
  148. nonce = long_to_bytes(self.counter.next_value(), 8)
  149. data = ''.join((nonce, AES.new(self.enc_key, AES.MODE_CTR, '',
  150. counter=self.counter).encrypt(data)))
  151. hash = HMAC.new(self.enc_hmac_key, data, SHA256).digest()
  152. return ''.join(('\0', hash, data))
  153. def decrypt(self, id, data):
  154. if data[0] != '\0':
  155. raise IntegrityError('Invalid encryption envelope')
  156. hash = data[1:33]
  157. if HMAC.new(self.enc_hmac_key, data[33:], SHA256).digest() != hash:
  158. raise IntegrityError('Encryption envelope checksum mismatch')
  159. nonce = bytes_to_long(data[33:41])
  160. counter = Counter.new(64, initial_value=nonce, prefix=PREFIX)
  161. data = zlib.decompress(AES.new(self.enc_key, AES.MODE_CTR, counter=counter).decrypt(data[41:]))
  162. if id and HMAC.new(self.id_key, data, SHA256).digest() != id:
  163. raise IntegrityError('Chunk id verification failed')
  164. return data