key.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  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 pbkdf2 import pbkdf2
  8. from Crypto.Cipher import AES
  9. from Crypto.Hash import SHA256, HMAC
  10. from Crypto.Util import Counter
  11. from Crypto.Util.number import bytes_to_long, long_to_bytes
  12. from Crypto.Random import get_random_bytes
  13. from .helpers import IntegrityError
  14. class Key(object):
  15. FILE_ID = 'DARC KEY'
  16. def __init__(self, store=None):
  17. if store:
  18. self.open(store)
  19. def open(self, store):
  20. path = os.path.join(os.path.expanduser('~'),
  21. '.darc', 'keys', store.id.encode('hex'))
  22. with open(path, 'rb') as fd:
  23. lines = fd.readlines()
  24. if not lines[0].startswith(self.FILE_ID) != self.FILE_ID:
  25. raise ValueError('Not a DARC key file')
  26. self.store_id = lines[0][len(self.FILE_ID):].strip().decode('hex')
  27. cdata = (''.join(lines[1:])).decode('base64')
  28. self.password = ''
  29. data = self.decrypt_key_file(cdata, '')
  30. while not data:
  31. self.password = getpass('Key password: ')
  32. if not self.password:
  33. raise Exception('Key decryption failed')
  34. data = self.decrypt_key_file(cdata, self.password)
  35. if not data:
  36. print 'Incorrect password'
  37. key = msgpack.unpackb(data)
  38. assert key['version'] == 1
  39. self.store_id = key['store_id']
  40. self.enc_key = key['enc_key']
  41. self.enc_hmac_key = key['enc_hmac_key']
  42. self.id_key = key['id_key']
  43. self.archive_key = key['archive_key']
  44. self.chunk_seed = key['chunk_seed']
  45. self.counter = Counter.new(128, initial_value=bytes_to_long(os.urandom(16)), allow_wraparound=True)
  46. def encrypt_key_file(self, data, password):
  47. salt = get_random_bytes(32)
  48. iterations = 2000
  49. key = pbkdf2(password, salt, 32, iterations, hashlib.sha256)
  50. hash = HMAC.new(key, data, SHA256).digest()
  51. cdata = AES.new(key, AES.MODE_CTR, counter=Counter.new(128)).encrypt(data)
  52. d = {
  53. 'version': 1,
  54. 'salt': salt,
  55. 'iterations': iterations,
  56. 'algorithm': 'SHA256',
  57. 'hash': hash,
  58. 'data': cdata,
  59. }
  60. return msgpack.packb(d)
  61. def decrypt_key_file(self, data, password):
  62. d = msgpack.unpackb(data)
  63. assert d['version'] == 1
  64. assert d['algorithm'] == 'SHA256'
  65. key = pbkdf2(password, d['salt'], 32, d['iterations'], hashlib.sha256)
  66. data = AES.new(key, AES.MODE_CTR, counter=Counter.new(128)).decrypt(d['data'])
  67. if HMAC.new(key, data, SHA256).digest() != d['hash']:
  68. return None
  69. return data
  70. def save(self, path, password):
  71. key = {
  72. 'version': 1,
  73. 'store_id': self.store_id,
  74. 'enc_key': self.enc_key,
  75. 'enc_hmac_key': self.enc_hmac_key,
  76. 'id_key': self.enc_key,
  77. 'archive_key': self.enc_key,
  78. 'chunk_seed': self.chunk_seed,
  79. }
  80. data = self.encrypt_key_file(msgpack.packb(key), password)
  81. with open(path, 'wb') as fd:
  82. fd.write('%s %s\n' % (self.FILE_ID, self.store_id.encode('hex')))
  83. fd.write(data.encode('base64'))
  84. print 'Key chain "%s" created' % path
  85. def chpass(self):
  86. password, password2 = 1, 2
  87. while password != password2:
  88. password = getpass('New password: ')
  89. password2 = getpass('New password again: ')
  90. if password != password2:
  91. print 'Passwords do not match'
  92. self.save(self.path, password)
  93. return 0
  94. @staticmethod
  95. def create(store):
  96. path = os.path.join(os.path.expanduser('~'),
  97. '.darc', 'keys', store.id.encode('hex'))
  98. if os.path.exists(path):
  99. print '%s already exists' % path
  100. return 1
  101. password, password2 = 1, 2
  102. while password != password2:
  103. password = getpass('Keychain password: ')
  104. password2 = getpass('Keychain password again: ')
  105. if password != password2:
  106. print 'Passwords do not match'
  107. key = Key()
  108. key.store_id = store.id
  109. # Chunk AES256 encryption key
  110. key.enc_key = get_random_bytes(32)
  111. # Chunk encryption HMAC key
  112. key.enc_hmac_key = get_random_bytes(32)
  113. # Chunk id HMAC key
  114. key.id_key = get_random_bytes(32)
  115. # Archive name HMAC key
  116. key.archive_key = get_random_bytes(32)
  117. # Chunkifier seed
  118. key.chunk_seed = bytes_to_long(get_random_bytes(4)) & 0x7fffffff
  119. key.save(path, password)
  120. return 0
  121. def id_hash(self, data):
  122. """Return HMAC hash using the "id" HMAC key
  123. """
  124. return HMAC.new(self.id_key, data, SHA256).digest()
  125. def archive_hash(self, data):
  126. """Return HMAC hash using the "archive" HMAC key
  127. """
  128. return HMAC.new(self.archive_key, data, SHA256).digest()
  129. def encrypt(self, data):
  130. data = zlib.compress(data)
  131. nonce = long_to_bytes(self.counter.next_value(), 16)
  132. data = ''.join((nonce, AES.new(self.enc_key, AES.MODE_CTR, '',
  133. counter=self.counter).encrypt(data)))
  134. hash = HMAC.new(self.enc_hmac_key, data, SHA256).digest()
  135. return ''.join(('\0', hash, data)), hash
  136. def decrypt(self, data):
  137. assert data[0] == '\0'
  138. hash = data[1:33]
  139. if HMAC.new(self.enc_hmac_key, data[33:], SHA256).digest() != hash:
  140. raise IntegrityError('Encryption integrity error')
  141. nonce = bytes_to_long(data[33:49])
  142. counter = Counter.new(128, initial_value=nonce, allow_wraparound=True)
  143. data = AES.new(self.enc_key, AES.MODE_CTR, counter=counter).decrypt(data[49:])
  144. return zlib.decompress(data), hash