使用 Keepass 几年后,我想到了一个可以实现的好主意。我的数据库中有 300 多条记录,如果能知道其中哪些记录仍然有效就好了。在记录上设置过期标志将有助于提醒我检查记录是否有效,并可能鼓励我更改密码。有没有办法查看整个数据库并对每条记录执行以下操作?
- 检查是否设置了过期标志。
- 如果未设置,则将创建(或修改)时间添加X年数。
- 设置过期标志。
- 将过期时间设置为步骤2中计算的值。
- 继续数据库中的下一条记录。
已收到以下回复保罗他说:“不能使用 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()