keychain.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  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.PublicKey import RSA
  11. from Crypto.Util import Counter
  12. from Crypto.Util.number import bytes_to_long, long_to_bytes
  13. from . import PACKET_ENCRYPT_READ, PACKET_ENCRYPT_CREATE
  14. from .helpers import IntegrityError, zero_pad
  15. from .oaep import OAEP
  16. class Keychain(object):
  17. FILE_ID = 'DARC KEYCHAIN'
  18. CREATE = '\1'
  19. READ = '\2'
  20. def __init__(self, path=None):
  21. self._key_cache = {}
  22. self.read_key = os.urandom(32)
  23. self.create_key = os.urandom(32)
  24. self.counter = Counter.new(64, prefix='\0' * 8)
  25. self.aes_id = self.rsa_read = self.rsa_create = None
  26. self.path = path
  27. if path:
  28. self.open(path)
  29. def get_chunkify_seed(self):
  30. return bytes_to_long(self.aes_id[:4]) & 0x7fffffff
  31. def open(self, path):
  32. print 'Opening keychain "%s"' % path
  33. with open(path, 'rb') as fd:
  34. if fd.read(len(self.FILE_ID)) != self.FILE_ID:
  35. raise ValueError('Not a keychain')
  36. cdata = fd.read()
  37. self.password = ''
  38. data = self.decrypt_keychain(cdata, '')
  39. while not data:
  40. self.password = getpass('Keychain password: ')
  41. if not self.password:
  42. raise Exception('Keychain decryption failed')
  43. data = self.decrypt_keychain(cdata, self.password)
  44. if not data:
  45. print 'Incorrect password'
  46. chain = msgpack.unpackb(data)
  47. assert chain['version'] == 1
  48. self.aes_id = chain['aes_id']
  49. self.rsa_read = RSA.importKey(chain['rsa_read'])
  50. self.rsa_create = RSA.importKey(chain['rsa_create'])
  51. self.read_encrypted = OAEP(256, hash=SHA256).encode(self.read_key, os.urandom(32))
  52. self.read_encrypted = zero_pad(self.rsa_read.encrypt(self.read_encrypted, '')[0], 256)
  53. self.create_encrypted = OAEP(256, hash=SHA256).encode(self.create_key, os.urandom(32))
  54. self.create_encrypted = zero_pad(self.rsa_create.encrypt(self.create_encrypted, '')[0], 256)
  55. def encrypt_keychain(self, data, password):
  56. salt = os.urandom(32)
  57. iterations = 2000
  58. key = pbkdf2(password, salt, 32, iterations, hashlib.sha256)
  59. hash = HMAC.new(key, data, SHA256).digest()
  60. cdata = AES.new(key, AES.MODE_CTR, counter=Counter.new(128)).encrypt(data)
  61. d = {
  62. 'version': 1,
  63. 'salt': salt,
  64. 'iterations': iterations,
  65. 'algorithm': 'SHA256',
  66. 'hash': hash,
  67. 'data': cdata,
  68. }
  69. return msgpack.packb(d)
  70. def decrypt_keychain(self, data, password):
  71. d = msgpack.unpackb(data)
  72. assert d['version'] == 1
  73. assert d['algorithm'] == 'SHA256'
  74. key = pbkdf2(password, d['salt'], 32, d['iterations'], hashlib.sha256)
  75. data = AES.new(key, AES.MODE_CTR, counter=Counter.new(128)).decrypt(d['data'])
  76. if HMAC.new(key, data, SHA256).digest() != d['hash']:
  77. return None
  78. return data
  79. def save(self, path, password):
  80. chain = {
  81. 'version': 1,
  82. 'aes_id': self.aes_id,
  83. 'rsa_read': self.rsa_read.exportKey('PEM'),
  84. 'rsa_create': self.rsa_create.exportKey('PEM'),
  85. }
  86. data = self.encrypt_keychain(msgpack.packb(chain), password)
  87. with open(path, 'wb') as fd:
  88. fd.write(self.FILE_ID)
  89. fd.write(data)
  90. print 'Key chain "%s" saved' % path
  91. def restrict(self, path):
  92. if os.path.exists(path):
  93. print '%s already exists' % path
  94. return 1
  95. self.rsa_read = self.rsa_read.publickey()
  96. self.save(path, self.password)
  97. return 0
  98. def chpass(self):
  99. password, password2 = 1, 2
  100. while password != password2:
  101. password = getpass('New password: ')
  102. password2 = getpass('New password again: ')
  103. if password != password2:
  104. print 'Passwords do not match'
  105. self.save(self.path, password)
  106. return 0
  107. @staticmethod
  108. def generate(path):
  109. if os.path.exists(path):
  110. print '%s already exists' % path
  111. return 1
  112. password, password2 = 1, 2
  113. while password != password2:
  114. password = getpass('Keychain password: ')
  115. password2 = getpass('Keychain password again: ')
  116. if password != password2:
  117. print 'Passwords do not match'
  118. chain = Keychain()
  119. print 'Generating keychain'
  120. chain.aes_id = os.urandom(32)
  121. chain.rsa_read = RSA.generate(2048)
  122. chain.rsa_create = RSA.generate(2048)
  123. chain.save(path, password)
  124. return 0
  125. def id_hash(self, data):
  126. """Return HMAC hash using the "id" AES key
  127. """
  128. return HMAC.new(self.aes_id, data, SHA256).digest()
  129. def encrypt(self, magic, data):
  130. """Helper function used by `encrypt_read` and `encrypt_create`
  131. """
  132. data = zlib.compress(data)
  133. nonce = long_to_bytes(self.counter.next_value(), 8)
  134. if magic & PACKET_ENCRYPT_READ:
  135. data = ''.join((nonce, self.read_encrypted,
  136. AES.new(self.read_key, AES.MODE_CTR, '',
  137. counter=self.counter).encrypt(data)))
  138. elif magic & PACKET_ENCRYPT_CREATE:
  139. data = ''.join((nonce, self.create_encrypted,
  140. AES.new(self.create_key, AES.MODE_CTR, '',
  141. counter=self.counter).encrypt(data)))
  142. hash = self.id_hash(data)
  143. return ''.join((chr(magic), hash, data)), hash
  144. def _decrypt_key(self, data, rsa_key):
  145. """Helper function used by `decrypt`
  146. """
  147. try:
  148. return self._key_cache[data]
  149. except KeyError:
  150. self._key_cache[data] = OAEP(256, hash=SHA256).decode(rsa_key.decrypt(data))
  151. return self._key_cache[data]
  152. def decrypt(self, data):
  153. """Decrypt `data` previously encrypted by `encrypt_create` or `encrypt_read`
  154. """
  155. magic = ord(data[0])
  156. hash = data[1:33]
  157. if self.id_hash(data[33:]) != hash:
  158. raise IntegrityError('Encryption integrity error')
  159. nonce = bytes_to_long(data[33:41])
  160. counter = Counter.new(64, prefix='\0' * 8, initial_value=nonce)
  161. if magic & PACKET_ENCRYPT_READ:
  162. key = self._decrypt_key(data[41:297], self.rsa_read)
  163. elif magic & PACKET_ENCRYPT_CREATE:
  164. key = self._decrypt_key(data[41:297], self.rsa_create)
  165. else:
  166. raise Exception('Unknown pack magic %d found' % magic)
  167. data = AES.new(key, AES.MODE_CTR, counter=counter).decrypt(data[297:])
  168. return magic, zlib.decompress(data), hash