def __init__(self, filepath=None, password=None, keyfile=None, read_only=False, new = False): """ Initialize a new or an existing database. If a 'filepath' and a 'masterkey' is passed 'load' will try to open a database. If 'True' is passed to 'read_only' the database will open read-only. It's also possible to create a new one, just pass 'True' to new. This will be ignored if a filepath and a masterkey is given this will be ignored. """ if filepath is not None and password is None and keyfile is None: raise KPError('Missing argument: Password or keyfile ' 'needed additionally to open an existing database!') elif type(read_only) is not bool or type(new) is not bool: raise KPError('read_only and new must be bool') elif ((filepath is not None and type(filepath) is not str) or (type(password) is not str and password is not None) or (type(keyfile) is not str and keyfile is not None)): raise KPError('filepath, masterkey and keyfile must be a string') elif (filepath is None and password is None and keyfile is None and new is False): raise KPError('Either an existing database should be opened or ' 'a new should be created.') self.groups = [] self.entries = [] self.root_group = v1Group() self.read_only = read_only self.filepath = filepath self.password = password self.keyfile = keyfile # This are attributes that are needed internally. You should not # change them directly, it could damage the database! self._group_order = [] self._entry_order = [] self._signature1 = 0x9AA2D903 self._signature2 = 0xB54BFB65 self._enc_flag = 2 self._version = 0x00030002 self._final_randomseed = '' self._enc_iv = '' self._num_groups = 1 self._num_entries = 0 self._contents_hash = '' Random.atfork() self._transf_randomseed = Random.get_random_bytes(32) self._key_transf_rounds = 150000 # Due to the design of KeePass, at least one group is needed. if new is True: self._group_order = [("id", 1), (1, 4), (2, 9), (7, 4), (8, 2), (0xFFFF, 0)] group = v1Group(1, 'Internet', 1, self, parent = self.root_group) self.root_group.children.append(group) self.groups.append(group)
def create_group(self, title=None, parent=None, image=1, y=2999, mon=12, d=28, h=23, min_=59, s=59): """This method creates a new group. A group title is needed or no group will be created. If a parent is given, the group will be created as a sub-group. title must be a string, image an unsigned int >0 and parent a v1Group. With y, mon, d, h, min_ and s you can set an expiration date like on entries. """ if title is None: raise KPError("Need a group title to create a group.") elif type(title) is not str or image < 1 or(parent is not None and \ type(parent) is not v1Group) or type(image) is not int: raise KPError("Wrong type or value for title or image or parent") id_ = 1 for i in self.groups: if i.id_ >= id_: id_ = i.id_ + 1 group = v1Group(id_, title, image, self) group.creation = datetime.now().replace(microsecond=0) group.last_mod = datetime.now().replace(microsecond=0) group.last_access = datetime.now().replace(microsecond=0) if group.set_expire(y, mon, d, h, min_, s) is False: group.set_expire() # If no parent is given, just append the new group at the end if parent is None: group.parent = self.root_group self.root_group.children.append(group) group.level = 0 self.groups.append(group) # Else insert the group behind the parent else: if parent in self.groups: parent.children.append(group) group.parent = parent group.level = parent.level + 1 self.groups.insert(self.groups.index(parent) + 1, group) else: raise KPError("Given parent doesn't exist") self._num_groups += 1 return True
def lock(self): """This method locks the database.""" self.password = None self.keyfile = None self.groups[:] = [] self.entries[:] = [] self._group_order[:] = [] self._entry_order[:] = [] self.root_group = v1Group() self._num_groups = 1 self._num_entries = 0 return True
def create_group(self, title = None, parent = None, image = 1, y = 2999, mon = 12, d = 28, h = 23, min_ = 59, s = 59): """This method creates a new group. A group title is needed or no group will be created. If a parent is given, the group will be created as a sub-group. title must be a string, image an unsigned int >0 and parent a v1Group. With y, mon, d, h, min_ and s you can set an expiration date like on entries. """ if title is None: raise KPError("Need a group title to create a group.") elif type(title) is not str or image < 1 or(parent is not None and \ type(parent) is not v1Group) or type(image) is not int: raise KPError("Wrong type or value for title or image or parent") id_ = 1 for i in self.groups: if i.id_ >= id_: id_ = i.id_ + 1 group = v1Group(id_, title, image, self) group.creation = datetime.now().replace(microsecond=0) group.last_mod = datetime.now().replace(microsecond=0) group.last_access = datetime.now().replace(microsecond=0) if group.set_expire(y, mon, d, h, min_, s) is False: group.set_expire() # If no parent is given, just append the new group at the end if parent is None: group.parent = self.root_group self.root_group.children.append(group) group.level = 0 self.groups.append(group) # Else insert the group behind the parent else: if parent in self.groups: parent.children.append(group) group.parent = parent group.level = parent.level+1 self.groups.insert(self.groups.index(parent)+1, group) else: raise KPError("Given parent doesn't exist") self._num_groups += 1 return True
def load(self, buf = None): """This method opens an existing database. self.password/self.keyfile and self.filepath must be set. """ if self.password is None and self.keyfile is None: raise KPError('Need a password or keyfile') elif self.filepath is None and buf is None: raise KPError('Can only load an existing database!') if buf is None: buf = self.read_buf() # The header is 124 bytes long, the rest is content header = buf[:124] crypted_content = buf[124:] del buf # The header holds two signatures if not (struct.unpack('<I', header[:4])[0] == 0x9AA2D903 and struct.unpack('<I', header[4:8])[0] == 0xB54BFB65): del crypted_content del header raise KPError('Wrong signatures!') # Unpack the header self._enc_flag = struct.unpack('<I', header[8:12])[0] self._version = struct.unpack('<I', header[12:16])[0] self._final_randomseed = struct.unpack('<16s', header[16:32])[0] self._enc_iv = struct.unpack('<16s', header[32:48])[0] self._num_groups = struct.unpack('<I', header[48:52])[0] self._num_entries = struct.unpack('<I', header[52:56])[0] self._contents_hash = struct.unpack('<32s', header[56:88])[0] self._transf_randomseed = struct.unpack('<32s', header[88:120])[0] self._key_transf_rounds = struct.unpack('<I', header[120:124])[0] del header # Check if the database is supported if self._version & 0xFFFFFF00 != 0x00030002 & 0xFFFFFF00: del crypted_content raise KPError('Unsupported file version!') #Actually, only AES is supported. elif not self._enc_flag & 2: del crypted_content raise KPError('Unsupported file encryption!') if self.password is None: masterkey = self._get_filekey() elif self.password is not None and self.keyfile is not None: passwordkey = self._get_passwordkey() filekey = self._get_filekey() sha = SHA256.new() sha.update(passwordkey+filekey) masterkey = sha.digest() else: masterkey = self._get_passwordkey() # Create the key that is needed to... final_key = self._transform_key(masterkey) # ...decrypt the content decrypted_content = self._cbc_decrypt(final_key, crypted_content) # Check if decryption failed if ((len(decrypted_content) > 2147483446) or (len(decrypted_content) == 0 and self._num_groups > 0)): del decrypted_content del crypted_content raise KPError("Decryption failed!\nThe key is wrong or the file is" " damaged.") sha_obj = SHA256.new() sha_obj.update(decrypted_content) if not self._contents_hash == sha_obj.digest(): del masterkey del final_key raise KPError("Hash test failed.\nThe key is wrong or the file is " "damaged.") del masterkey del final_key # Read out the groups pos = 0 levels = [] cur_group = 0 group = v1Group() while cur_group < self._num_groups: # Every group is made up of single fields field_type = struct.unpack('<H', decrypted_content[:2])[0] decrypted_content = decrypted_content[2:] pos += 2 # Check if offset is alright if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') field_size = struct.unpack('<I', decrypted_content[:4])[0] decrypted_content = decrypted_content[4:] pos += 4 if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G2]') # Finally read out the content b_ret = self._read_group_field(group, levels, field_type, field_size, decrypted_content) # If the end of a group is reached append it to the groups array if field_type == 0xFFFF and b_ret == True: group.db = self self.groups.append(group) group = v1Group() cur_group += 1 decrypted_content = decrypted_content[field_size:] if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') # Now the same with the entries cur_entry = 0 entry = v1Entry() while cur_entry < self._num_entries: field_type = struct.unpack('<H', decrypted_content[:2])[0] decrypted_content = decrypted_content[2:] pos += 2 if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') field_size = struct.unpack('<I', decrypted_content[:4])[0] decrypted_content = decrypted_content[4:] pos += 4 if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G2]') b_ret = self._read_entry_field(entry, field_type, field_size, decrypted_content) if field_type == 0xFFFF and b_ret == True: self.entries.append(entry) if entry.group_id is None: del decrypted_content del crypted_content raise KPError("Found entry without group!") entry = v1Entry() cur_entry += 1 decrypted_content = decrypted_content[field_size:] pos += field_size if pos >= len(crypted_content)+124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') if self._create_group_tree(levels) is False: del decrypted_content del crypted_content return False del decrypted_content del crypted_content if self.filepath is not None: with open(self.filepath+'.lock', 'w') as handler: handler.write('') return True
def load(self, buf=None): """This method opens an existing database. self.password/self.keyfile and self.filepath must be set. """ if self.password is None and self.keyfile is None: raise KPError('Need a password or keyfile') elif self.filepath is None and buf is None: raise KPError('Can only load an existing database!') if buf is None: buf = self.read_buf() # The header is 124 bytes long, the rest is content header = buf[:124] crypted_content = buf[124:] del buf # The header holds two signatures if not (struct.unpack('<I', header[:4])[0] == 0x9AA2D903 and struct.unpack('<I', header[4:8])[0] == 0xB54BFB65): del crypted_content del header raise KPError('Wrong signatures!') # Unpack the header self._enc_flag = struct.unpack('<I', header[8:12])[0] self._version = struct.unpack('<I', header[12:16])[0] self._final_randomseed = struct.unpack('<16s', header[16:32])[0] self._enc_iv = struct.unpack('<16s', header[32:48])[0] self._num_groups = struct.unpack('<I', header[48:52])[0] self._num_entries = struct.unpack('<I', header[52:56])[0] self._contents_hash = struct.unpack('<32s', header[56:88])[0] self._transf_randomseed = struct.unpack('<32s', header[88:120])[0] self._key_transf_rounds = struct.unpack('<I', header[120:124])[0] del header # Check if the database is supported if self._version & 0xFFFFFF00 != 0x00030002 & 0xFFFFFF00: del crypted_content raise KPError('Unsupported file version!') #Actually, only AES is supported. elif not self._enc_flag & 2: del crypted_content raise KPError('Unsupported file encryption!') if self.password is None: masterkey = self._get_filekey() elif self.password is not None and self.keyfile is not None: passwordkey = self._get_passwordkey() filekey = self._get_filekey() sha = SHA256.new() sha.update(passwordkey + filekey) masterkey = sha.digest() else: masterkey = self._get_passwordkey() # Create the key that is needed to... final_key = self._transform_key(masterkey) # ...decrypt the content decrypted_content = self._cbc_decrypt(final_key, crypted_content) # Check if decryption failed if ((len(decrypted_content) > 2147483446) or (len(decrypted_content) == 0 and self._num_groups > 0)): del decrypted_content del crypted_content raise KPError("Decryption failed!\nThe key is wrong or the file is" " damaged.") sha_obj = SHA256.new() sha_obj.update(decrypted_content) if not self._contents_hash == sha_obj.digest(): del masterkey del final_key raise KPError("Hash test failed.\nThe key is wrong or the file is " "damaged.") del masterkey del final_key # Read out the groups pos = 0 levels = [] cur_group = 0 group = v1Group() while cur_group < self._num_groups: # Every group is made up of single fields field_type = struct.unpack('<H', decrypted_content[:2])[0] decrypted_content = decrypted_content[2:] pos += 2 # Check if offset is alright if pos >= len(crypted_content) + 124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') field_size = struct.unpack('<I', decrypted_content[:4])[0] decrypted_content = decrypted_content[4:] pos += 4 if pos >= len(crypted_content) + 124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G2]') # Finally read out the content b_ret = self._read_group_field(group, levels, field_type, field_size, decrypted_content) # If the end of a group is reached append it to the groups array if field_type == 0xFFFF and b_ret == True: group.db = self self.groups.append(group) group = v1Group() cur_group += 1 decrypted_content = decrypted_content[field_size:] if pos >= len(crypted_content) + 124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') # Now the same with the entries cur_entry = 0 entry = v1Entry() while cur_entry < self._num_entries: field_type = struct.unpack('<H', decrypted_content[:2])[0] decrypted_content = decrypted_content[2:] pos += 2 if pos >= len(crypted_content) + 124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') field_size = struct.unpack('<I', decrypted_content[:4])[0] decrypted_content = decrypted_content[4:] pos += 4 if pos >= len(crypted_content) + 124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G2]') b_ret = self._read_entry_field(entry, field_type, field_size, decrypted_content) if field_type == 0xFFFF and b_ret == True: self.entries.append(entry) if entry.group_id is None: del decrypted_content del crypted_content raise KPError("Found entry without group!") entry = v1Entry() cur_entry += 1 decrypted_content = decrypted_content[field_size:] pos += field_size if pos >= len(crypted_content) + 124: del decrypted_content del crypted_content raise KPError('Unexpected error: Offset is out of range.[G1]') if self._create_group_tree(levels) is False: del decrypted_content del crypted_content return False del decrypted_content del crypted_content if self.filepath is not None: with open(self.filepath + '.lock', 'w') as handler: handler.write('') return True