/storage.py

https://bitbucket.org/miphreal/safep · Python · 154 lines · 110 code · 36 blank · 8 comment · 25 complexity · 8dc990ef3e5cb1ab02812738cb5f8e34 MD5 · raw file

  1. __author__ = 'miph'
  2. import os
  3. import base64
  4. b64e = lambda s: base64.encodestring(s).rstrip()
  5. b64d = base64.decodestring
  6. from functools import partial
  7. from Crypto.Cipher import AES, Blowfish
  8. try:
  9. from md5 import md5 as hash_func
  10. except ImportError:
  11. from hashlib import md5 as hash_func
  12. def _cipher(algo, secret):
  13. h = hash_func()
  14. h.update(secret)
  15. hash = h.hexdigest()
  16. l = len(secret)
  17. if l<32:
  18. secret = secret+hash[l:]
  19. else:
  20. secret = secret[:32]
  21. return algo.new(secret)
  22. aes = partial(_cipher, AES)
  23. blowfish = partial(_cipher, Blowfish)
  24. def pack(data):
  25. data = b64e(data)
  26. m = len(data) % 16
  27. return data + (16-m)*'~'
  28. def unpack(data):
  29. return b64d(data.rstrip('~'))
  30. def encode(cipher, data):
  31. return b64e(cipher.encrypt(pack(data))).replace('\n','')
  32. def decode(cipher, data):
  33. return unpack(cipher.decrypt(b64d(data)))
  34. def parse_record(data):
  35. return tuple([b64d(r) for r in data.split(',')])
  36. def build_record(name, user, password, keywords):
  37. return b64e(name),b64e(user),b64e(password),b64e(keywords)
  38. # cipher order:
  39. # save: text -> b64e -> append ~~~ -> encrypt -> b64e
  40. # load: b64d -> decrypt -> rstrip ~~~ -> b64d -> text
  41. class PasswordError(Exception):
  42. pass
  43. class SafeStorage:
  44. #NOTE. record fields: name, user, password, keywords
  45. def __init__(self, db, passwd):
  46. self._file_db = db
  47. #aes and blowfish ciphers
  48. self._aes = aes(passwd)
  49. self._blowfish = blowfish(passwd)
  50. mode = 'r' if os.path.exists(db) else 'w+'
  51. self._map = []
  52. with open(self._file_db, mode) as f:
  53. for line in f.readlines():
  54. if line:
  55. try:
  56. self._map.append(
  57. parse_record(
  58. decode(self._aes, line.rstrip())))
  59. except Exception:
  60. raise PasswordError
  61. @property
  62. def file_path(self):
  63. return self._file_db
  64. def close(self):
  65. pass
  66. def get_records(self, indx_list, with_passwords=False):
  67. result = [self._map[i] for i in indx_list]
  68. if with_passwords:
  69. return [(i[0],i[1],decode(self._blowfish,i[2]),i[3]) for i in result]
  70. return [(i[0],i[1],'******',i[3]) for i in result]
  71. def get_record(self, indx, with_passwords=False):
  72. i = self._map[indx]
  73. if with_passwords:
  74. return i[0], i[1], decode(self._blowfish, i[2]), i[3]
  75. return i[0], i[1], '******', i[3]
  76. def search(self, word):
  77. result = []
  78. for i, record in enumerate(self._map):
  79. if any(word in item for item in record) or word == str(i):
  80. result.append(i)
  81. return result
  82. def add(self, name, user, password, keywords):
  83. password = encode(self._blowfish, password)
  84. builded = ','.join(build_record(name, user, password, keywords))
  85. encoded = encode(self._aes, builded)
  86. self._map.append((name, user, password, keywords))
  87. #saving to file
  88. with open(self._file_db, 'a') as f:
  89. f.write(encoded)
  90. f.write('\n')
  91. def edit(self, indx, record):
  92. self._map[indx] = (record[0],record[1],encode(self._blowfish,record[2]),record[3])
  93. self._resave()
  94. def delete(self, indx):
  95. self._map.pop(indx)
  96. self._resave()
  97. def change_password(self, password):
  98. new_records = []
  99. new_aes = aes(password)
  100. new_blowfish = blowfish(password)
  101. for (name,user,passwd,kw) in self._map:
  102. p = decode(self._blowfish, passwd)
  103. passwd = encode(new_blowfish, p)
  104. new_records.append((name, user, passwd, kw))
  105. #atomic{
  106. self._aes = new_aes
  107. self._blowfish = new_blowfish
  108. self._map = new_records
  109. self._resave()
  110. #}
  111. def _resave(self):
  112. with open(self._file_db,'w') as f:
  113. f.truncate()
  114. for record in self._map:
  115. builded = ','.join(build_record(*record))
  116. encoded = encode(self._aes, builded)
  117. f.write(encoded)
  118. f.write('\n')