Пример #1
0
    def save(self, dbfile=None, password=None, keyfile=None):
        """
        Save the database to specified file/stream with password and/or keyfile.
         
        :param dbfile: The path to the file we wish to save.
        :type dbfile: The path to the database file or a file-like object.
        
        :param password: The password to use for the database encryption key.
        :type password: str
        :param keyfile: The path to keyfile (or a stream) to use instead of or in conjunction with password for encryption key.
        :type keyfile: str or file-like object
        :raise keepassdb.exc.ReadOnlyDatabase: If database was opened with readonly flag.
        """
        if self.readonly:
            # We might wish to make this more sophisticated.  E.g. if a new path is specified
            # as a parameter, then it's probably ok to ignore a readonly flag?  In general
            # this flag doens't make a ton of sense for a library ...
            raise exc.ReadOnlyDatabase()
        
        if dbfile is not None and not hasattr(dbfile, 'write'):
            self.filepath = dbfile
            
        if password is not None or self.keyfile is not None:
            # Do these together so we don't end up with some hybrid of old & new key material
            self.password = password
            self.keyfile = keyfile  
        else: 
            raise ValueError("Password and/or keyfile is required.")
        
        if self.filepath is None and dbfile is None:
            raise ValueError("Unable to save without target file.")
        
        buf = bytearray()
        
        # First, serialize the groups
        for group in self.groups:
            # Get the packed bytes
            group_struct = group.to_struct()
            self.log.debug("Group struct: {0!r}".format(group_struct))
            buf += group_struct.encode()
            
        # Then the entries.
        for entry in self.entries:
            entry_struct = entry.to_struct()
            buf += entry_struct.encode()

        # Hmmmm ... these defaults should probably be set elsewhere....?
        header = HeaderStruct()
        header.signature1 = const.DB_SIGNATURE1
        header.signature2 = const.DB_SIGNATURE2
        header.flags = header.AES
        header.version = 0x00030002
        header.key_enc_rounds = 50000
        header.seed_key = get_random_bytes(32)
        
        # Convert buffer to bytes for API simplicity
        buf = bytes(buf)
        
        # Generate new seed & vector; update content hash        
        header.encryption_iv = get_random_bytes(16)
        header.seed_rand = get_random_bytes(16)
        header.contents_hash = hashlib.sha256(buf).digest()
        
        self.log.debug("(Unencrypted) content: {0!r}".format(buf))
        self.log.debug("Generating hash for {0}-byte content: {1}".format(len(buf), hashlib.sha256(buf).digest()))
        # Update num groups/entries to match curr state
        header.nentries = len(self.entries)
        header.ngroups = len(self.groups)
        
        final_key = util.derive_key(seed_key=header.seed_key,
                                    seed_rand=header.seed_rand,
                                    rounds=header.key_enc_rounds,
                                    password=password, keyfile=keyfile)
        
        # FIXME: Remove this once we've tracked down issues.
        self.log.debug("(save) Final key: {0!r}, pass={1}".format(final_key, password))
        
        encrypted_content = util.encrypt_aes_cbc(buf, key=final_key, iv=header.encryption_iv)
        
        if hasattr(dbfile, 'write'):
            dbfile.write(header.encode() + encrypted_content)
        else:
            with open(self.filepath, "wb") as fp:
                fp.write(header.encode() + encrypted_content)
Пример #2
0
    def save(self, dbfile=None, password=None, keyfile=None):
        """
        Save the database to specified file/stream with password and/or keyfile.
         
        :param dbfile: The path to the file we wish to save.
        :type dbfile: The path to the database file or a file-like object.
        
        :param password: The password to use for the database encryption key.
        :type password: str
        :param keyfile: The path to keyfile (or a stream) to use instead of or in conjunction with password for encryption key.
        :type keyfile: str or file-like object
        :raise keepassdb.exc.ReadOnlyDatabase: If database was opened with readonly flag.
        """
        if self.readonly:
            # We might wish to make this more sophisticated.  E.g. if a new path is specified
            # as a parameter, then it's probably ok to ignore a readonly flag?  In general
            # this flag doens't make a ton of sense for a library ...
            raise exc.ReadOnlyDatabase()

        if dbfile is not None and not hasattr(dbfile, 'write'):
            self.filepath = dbfile

        if password is not None or self.keyfile is not None:
            # Do these together so we don't end up with some hybrid of old & new key material
            self.password = password
            self.keyfile = keyfile
        else:
            raise ValueError("Password and/or keyfile is required.")

        if self.filepath is None and dbfile is None:
            raise ValueError("Unable to save without target file.")

        buf = bytearray()

        # First, serialize the groups
        for group in self.groups:
            # Get the packed bytes
            group_struct = group.to_struct()
            self.log.debug("Group struct: {0!r}".format(group_struct))
            buf += group_struct.encode()

        # Then the entries.
        for entry in self.entries:
            entry_struct = entry.to_struct()
            buf += entry_struct.encode()

        # Hmmmm ... these defaults should probably be set elsewhere....?
        header = HeaderStruct()
        header.signature1 = const.DB_SIGNATURE1
        header.signature2 = const.DB_SIGNATURE2
        header.flags = header.AES
        header.version = 0x00030002
        header.key_enc_rounds = 50000
        header.seed_key = get_random_bytes(32)

        # Convert buffer to bytes for API simplicity
        buf = bytes(buf)

        # Generate new seed & vector; update content hash
        header.encryption_iv = get_random_bytes(16)
        header.seed_rand = get_random_bytes(16)
        header.contents_hash = hashlib.sha256(buf).digest()

        self.log.debug("(Unencrypted) content: {0!r}".format(buf))
        self.log.debug("Generating hash for {0}-byte content: {1}".format(
            len(buf),
            hashlib.sha256(buf).digest()))
        # Update num groups/entries to match curr state
        header.nentries = len(self.entries)
        header.ngroups = len(self.groups)

        final_key = util.derive_key(seed_key=header.seed_key,
                                    seed_rand=header.seed_rand,
                                    rounds=header.key_enc_rounds,
                                    password=password,
                                    keyfile=keyfile)

        # FIXME: Remove this once we've tracked down issues.
        self.log.debug("(save) Final key: {0!r}, pass={1}".format(
            final_key, password))

        encrypted_content = util.encrypt_aes_cbc(buf,
                                                 key=final_key,
                                                 iv=header.encryption_iv)

        if hasattr(dbfile, 'write'):
            dbfile.write(header.encode() + encrypted_content)
        else:
            with open(self.filepath, "wb") as fp:
                fp.write(header.encode() + encrypted_content)
Пример #3
0
    def load_from_buffer(self, buf, password=None, keyfile=None, readonly=False):
        """
        Load a database from passed-in buffer (bytes).

        :param buf: A string (bytes) of the database contents.
        :type buf: str
        :param password: The password for the database.
        :type password: str
        :param keyfile: Path to a keyfile (or a stream) that can be used instead of or in conjunction with password for database.
        :type keyfile: str or file-like object
        :param readonly: Whether to open the database read-only.
        :type readonly: bool
        """
        if password is None and keyfile is None:
            raise ValueError("Password and/or keyfile is required.")
        
        # Save these to use as defaults when saving the database
        self.password = password
        self.keyfile = keyfile
        
        # The header is 124 bytes long, the rest is content
        hdr_len = HeaderStruct.length
        header_bytes = buf[:hdr_len]
        crypted_content = buf[hdr_len:]
        
        self.header = HeaderStruct(header_bytes)
        
        self.log.debug("Extracted header: {0}".format(self.header))
        # Check if the database is supported
        if self.header.version & const.DB_SUPPORTED_VERSION_MASK != const.DB_SUPPORTED_VERSION & const.DB_SUPPORTED_VERSION_MASK:
            raise exc.UnsupportedDatabaseVersion('Unsupported file version: {0}'.format(hex(self.header.version)))
            
        #Actually, only AES is supported.
        if not self.header.flags & HeaderStruct.AES:
            raise exc.UnsupportedDatabaseEncryption('Only AES encryption is supported.')
        
        final_key = util.derive_key(seed_key=self.header.seed_key,
                                    seed_rand=self.header.seed_rand,
                                    rounds=self.header.key_enc_rounds,
                                    password=password, keyfile=keyfile)
        
        # FIXME: Remove this once we've tracked down issues.
        self.log.debug("(load) Final key: {0!r}, pass={1}".format(final_key, password))
        
        decrypted_content = util.decrypt_aes_cbc(crypted_content, key=final_key, iv=self.header.encryption_iv)
        
        # Check if decryption failed
        if ((len(decrypted_content) > const.DB_MAX_CONTENT_LEN) or
            (len(decrypted_content) == 0 and self.header.ngroups > 0)):
            raise exc.IncorrectKey("Decryption failed! The key is wrong or the file is damaged.")
        
        if not self.header.contents_hash == hashlib.sha256(decrypted_content).digest():
            self.log.debug("Decrypted content: {0!r}".format(decrypted_content))
            self.log.error("Hash mismatch. Header hash = {0!r}, hash of contents = {1!r}".format(self.header.contents_hash,                                                                                                 hashlib.sha256(decrypted_content).digest()))
            raise exc.AuthenticationError("Hash test failed. The key is wrong or the file is damaged.")
            
        # First thing (after header) are the group definitions.
        for _i in range(self.header.ngroups):
            gstruct = GroupStruct(decrypted_content)
            self.groups.append(Group.from_struct(gstruct))
            length = len(gstruct)
            decrypted_content = decrypted_content[length:]
        
        # Next come the entry definitions.
        for _i in range(self.header.nentries):
            estruct = EntryStruct(decrypted_content)
            self.entries.append(Entry.from_struct(estruct))
            length = len(estruct)
            decrypted_content = decrypted_content[length:]
            
        # Sets up the hierarchy, relates the group/entry model objects.
        self._bind_model()
Пример #4
0
    def load_from_buffer(self,
                         buf,
                         password=None,
                         keyfile=None,
                         readonly=False):
        """
        Load a database from passed-in buffer (bytes).

        :param buf: A string (bytes) of the database contents.
        :type buf: str
        :param password: The password for the database.
        :type password: str
        :param keyfile: Path to a keyfile (or a stream) that can be used instead of or in conjunction with password for database.
        :type keyfile: str or file-like object
        :param readonly: Whether to open the database read-only.
        :type readonly: bool
        """
        if password is None and keyfile is None:
            raise ValueError("Password and/or keyfile is required.")

        # Save these to use as defaults when saving the database
        self.password = password
        self.keyfile = keyfile

        # The header is 124 bytes long, the rest is content
        hdr_len = HeaderStruct.length
        header_bytes = buf[:hdr_len]
        crypted_content = buf[hdr_len:]

        self.header = HeaderStruct(header_bytes)

        self.log.debug("Extracted header: {0}".format(self.header))
        # Check if the database is supported
        if self.header.version & const.DB_SUPPORTED_VERSION_MASK != const.DB_SUPPORTED_VERSION & const.DB_SUPPORTED_VERSION_MASK:
            raise exc.UnsupportedDatabaseVersion(
                'Unsupported file version: {0}'.format(hex(
                    self.header.version)))

        #Actually, only AES is supported.
        if not self.header.flags & HeaderStruct.AES:
            raise exc.UnsupportedDatabaseEncryption(
                'Only AES encryption is supported.')

        final_key = util.derive_key(seed_key=self.header.seed_key,
                                    seed_rand=self.header.seed_rand,
                                    rounds=self.header.key_enc_rounds,
                                    password=password,
                                    keyfile=keyfile)

        # FIXME: Remove this once we've tracked down issues.
        self.log.debug("(load) Final key: {0!r}, pass={1}".format(
            final_key, password))

        decrypted_content = util.decrypt_aes_cbc(crypted_content,
                                                 key=final_key,
                                                 iv=self.header.encryption_iv)

        # Check if decryption failed
        if ((len(decrypted_content) > const.DB_MAX_CONTENT_LEN)
                or (len(decrypted_content) == 0 and self.header.ngroups > 0)):
            raise exc.IncorrectKey(
                "Decryption failed! The key is wrong or the file is damaged.")

        if not self.header.contents_hash == hashlib.sha256(
                decrypted_content).digest():
            self.log.debug(
                "Decrypted content: {0!r}".format(decrypted_content))
            self.log.error(
                "Hash mismatch. Header hash = {0!r}, hash of contents = {1!r}".
                format(self.header.contents_hash,
                       hashlib.sha256(decrypted_content).digest()))
            raise exc.AuthenticationError(
                "Hash test failed. The key is wrong or the file is damaged.")

        # First thing (after header) are the group definitions.
        for _i in range(self.header.ngroups):
            gstruct = GroupStruct(decrypted_content)
            self.groups.append(Group.from_struct(gstruct))
            length = len(gstruct)
            decrypted_content = decrypted_content[length:]

        # Next come the entry definitions.
        for _i in range(self.header.nentries):
            estruct = EntryStruct(decrypted_content)
            self.entries.append(Entry.from_struct(estruct))
            length = len(estruct)
            decrypted_content = decrypted_content[length:]

        # Sets up the hierarchy, relates the group/entry model objects.
        self._bind_model()