是否可以根据“创建”时间设置“过期”时间?

是否可以根据“创建”时间设置“过期”时间?

使用 Keepass 几年后,我想到了一个可以实现的好主意。我的数据库中有 300 多条记录,如果能知道其中哪些记录仍然有效就好了。在记录上设置过期标志将有助于提醒我检查记录是否有效,并可能鼓励我更改密码。有没有办法查看整个数据库并对每条记录执行以下操作?

  1. 检查是否设置了过期标志。
  2. 如果未设置,则将创建(或修改)时间添加X年数。
  3. 设置过期标志。
  4. 将过期时间设置为步骤2中计算的值。
  5. 继续数据库中的下一条记录。

已收到以下回复保罗他说:“不能使用 KeePass,但你可以使用 KPScript 和一些 PowerShell。”他附上了一个链接#1318 从列表中更改到期日期,但我对他推荐的工具缺乏经验,这只会让我产生更多疑问。有人可以提供进一步的帮助吗?(1)

附录

经过进一步研究,我们发现可以将数据库导出为 XML 格式。解析 XML 文件、更改该格式的数据,然后通过导入结果创建新数据库会不会更容易?如果使用已经提出的建议太困难,那么 XML 处理可能是一条更简单的途径。

答案1

是的,可以根据“创建”时间设置“过期”时间。通过使用程序,可以自动完成该过程。更改全局常量(韓譯語言詞数据库, 和密码)设置为与您的系统对应的值,然后运行。在此特定示例中,到期时间设置为上次修改日期后的五年。下面的程序改编自保罗的回答

#! /usr/bin/env python3
import datetime
import subprocess
import uuid


KPSCRIPT = r'C:\Program Files (x86)\KeePass Password Safe 2\KPScript.exe'
DATABASE = r'C:\Users\Stephen Paul Chappel\Documents\Database.kdbx'
PASSWORD = r'password'


def main():
    """Get KeePass entries, check each one, and change if required."""
    for line in ps(KPSCRIPT, '-c:ListEntries', DATABASE, '-pw:' + PASSWORD):
        key, value, *pair = line.split(': ', 1) + [None]
        if pair:
            if key == 'UUID':
                reference = uuid.UUID(value)
            elif key == 'TLM':
                tlm = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')
            elif key == 'EXP':
                if not {'False': False, 'True': True}[value]:
                    # Add 5 years to the last modification
                    # time for the expiry date and format.
                    te = tlm.replace(tlm.year + 5)
                    te = te.strftime('%Y-%m-%dT%H:%M:%S')
                    ps(
                        KPSCRIPT,
                        '-c:EditEntry',
                        DATABASE,
                        '-pw:' + PASSWORD,
                        '-refx-UUID:' + reference.hex,
                        '-setx-Expires:True',
                        '-setx-ExpiryTime:' + te
                    )
                del reference, tlm


def ps(*args):
    """Provide an interface for writing commands like those in PowerShell."""
    return subprocess.run(
        args,
        timeout=1,
        check=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True
    ).stdout.splitlines()


if __name__ == '__main__':
    main()

答案2

如果您想要采用 OOP 方法,您可以尝试使用以下原型:

#! /usr/bin/env python3
from datetime import datetime
from pathlib import Path
from subprocess import run, CalledProcessError
from sys import intern
from uuid import UUID

DATABASE = Path(r'C:\Users\Stephen Chappell\Documents\Accounts Live.kdbx')
PASSWORD = r'password'


def main():
    """Get KeePass entries, check each one, and change if required."""
    db = KPScript(DATABASE, PASSWORD)
    for entry in filter(needs_expiration, db.list_entries()):
        print('Updating:', {key: entry['S'][key] for key in
                            'Title UserName Password URL'.split()})
        # Add 5 years to the last modification
        # time for the expiry date and format.
        last_modified = entry['TLM']
        expiry_time = last_modified.replace(last_modified.year + 5)
        db.edit_entry(entry['UUID'], True, expiry_time)


def needs_expiration(entry):
    """Check if an entry needs its expiration set."""
    return not entry['EXP'] and \
        entry['GRPN'] not in {'Recycle Bin', 'Templates'}


def _field_to_datetime(value):
    """Convert a timestamp into a datetime object."""
    return datetime.strptime(value, KPScript.DATETIME_FORMAT)


class KPScript:
    """Reference: https://keepass.info/help/v2_dev/scr_sc_index.html"""

    EXE = Path(r'C:\Program Files\KeePass Password Safe 2\KPScript.exe')
    TIMEOUT = 2
    DATETIME_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
    FIELD_TYPES = {
        'UUID': UUID,
        'GRPU': UUID,
        'GRPN': intern,
        'S': lambda s: s,
        'TC': _field_to_datetime,
        'TLM': _field_to_datetime,
        'TE': _field_to_datetime,
        'EXP': {'True': True, 'False': False}.__getitem__
    }

    def __init__(self, db_path, password=None, keyfile=None):
        """Initialize the attributes of a KPScript instance."""
        self.__db_path = db_path
        self.__password = password
        self.__keyfile = keyfile

    def __do_command(self, name, kwargs=None):
        """Help run whatever command is specified by name."""
        args = [self.EXE, f'-c:{name}', self.__db_path]
        if self.__password is not None:
            args.append(f'-pw:{self.__password}')
        if self.__keyfile is not None:
            args.append(f'-keyfile:{self.__keyfile}')
        if kwargs is not None:
            for key, value in kwargs.items():
                args.append(f'-{key}:{value}')
        try:
            completed_process = run(
                args,
                capture_output=True,
                timeout=self.TIMEOUT,
                check=True,
                text=True
            )
        except CalledProcessError as error:
            print('Error:', error.stdout)
            raise error
        else:
            return completed_process.stdout.splitlines()

    def list_groups(self):
        """This command lists all groups in a format that easily
        machine-readable."""
        ...

    def list_entries(self):
        """This command lists all entries in a format that easily
        machine-readable."""

        def parse_line():
            nonlocal line_key, line_value
            try:
                new_line_key, new_line_value = line.split(': ', 1)
                if new_line_key not in self.FIELD_TYPES.keys() | {'OK'}:
                    raise ValueError()
            except ValueError:
                append_value()
            else:
                line_key, line_value = new_line_key, new_line_value
                if line_key == 'S':
                    parse_s()
                else:
                    entry[line_key] = line_value

        def parse_s():
            nonlocal s_key, s_value
            try:
                s_key, s_value = line_value.split(' = ', 1)
            except ValueError:
                raise SyntaxError(line_value)
            else:
                s_field = entry.setdefault('S', {})
                s_field[s_key] = s_value

        def append_value():
            if line_key == 'S':
                entry['S'][s_key] += '\n' + line
            else:
                entry[line_key] += '\n' + line

        def complete_entry():
            return entry.keys() == self.FIELD_TYPES.keys()

        def transform():
            for key, value in self.FIELD_TYPES.items():
                entry[key] = value(entry[key])
            return entry

        entry = {}
        line_key = line_value = ''
        s_key = s_value = ''
        for line in self.__do_command('ListEntries'):
            if line:
                parse_line()
            elif complete_entry():
                yield transform()
                entry = {}
            else:
                append_value()
        if len(entry) != 1 or 'OK' not in entry:
            raise ValueError('last entry is invalid')

    def get_entry_string(self):
        """Retrieves the value of an entry string field."""
        ...

    def add_entry(self):
        """This command adds an entry to the database."""
        ...

    def edit_entry(self, uuid, expires=None, expiry_time=None):
        """This command edits existing entries."""
        kwargs = {'refx-UUID': uuid.hex}
        if expires is not None:
            kwargs['setx-Expires'] = str(bool(expires)).lower()
        if expiry_time is not None:
            kwargs['setx-ExpiryTime'] = expiry_time.strftime(
                self.DATETIME_FORMAT)
        self.__do_command('EditEntry', kwargs)

    def move_entry(self):
        """This command moves one or more existing entries."""
        ...

    def delete_entry(self):
        """This command deletes one or more existing entries."""
        ...

    def delete_all_entries(self):
        """This command deletes all entries (in all subgroups)."""
        ...

    def import_(self):
        """This command imports a file into the database."""
        ...

    def export(self):
        """This command exports (parts of) the database."""
        ...

    def sync(self):
        """This command synchronizes the database with another one."""
        ...

    def change_master_key(self):
        """This command changes the master key of the database."""
        ...

    def detach_bins(self):
        """This command saves all entry attachments (into the directory of the
        database) and removes them from the database."""
        ...

    def gen_pw(self):
        """Generates passwords."""
        ...

    def estimate_quality(self):
        """Estimates the quality (in bits) of the password specified via the
        -text: parameter."""
        ...


if __name__ == '__main__':
    main()

相关内容