From 7dfbb3944ad4ab4d2723c37aa645796452a2b037 Mon Sep 17 00:00:00 2001 From: lthievenaz-keeper Date: Mon, 27 Jul 2026 08:48:55 +0100 Subject: [PATCH 1/3] Fix issues with blank Keepass password and Keyfile quotations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Keepass import does not support an empty Keepass password (empty string) → fixed - Keepass import breaks if you added the Keyfile wrapped in quotes → fixed --- keepercommander/importer/keepass/keepass.py | 28 ++++++++++----------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/keepercommander/importer/keepass/keepass.py b/keepercommander/importer/keepass/keepass.py index 4268ad67b..b70397d63 100644 --- a/keepercommander/importer/keepass/keepass.py +++ b/keepercommander/importer/keepass/keepass.py @@ -79,11 +79,11 @@ def get_folder(group): # type: (Group) -> str return path def do_import(self, filename, **kwargs): - password = getpass.getpass(prompt='...' + 'Keepass Password'.rjust(20) + ': ', stream=None) + password = getpass.getpass(prompt='...' + 'Keepass Password'.rjust(20) + ': ', stream=None) or None print('Press Enter if your Keepass file is not protected with a key file') keyfile = input('...' + 'Path to Key file'.rjust(20) + ': ') if keyfile: - keyfile = os.path.expanduser(keyfile) + keyfile = os.path.expanduser(keyfile.strip("\"'")) else: keyfile = None try: @@ -284,18 +284,15 @@ def to_keepass_value(keeper_value): # type: (any) -> str else: return XmlUtils.sanitize_xml_text(keeper_value) - def do_export(self, filename, records, file_password=None, **kwargs): - master_password = file_password - confirmed = True - while not master_password or not confirmed: - print('Choose password for your Keepass file') - master_password = getpass.getpass(prompt='...' + 'Keepass Password'.rjust(20) + ': ', stream=None) - print('\nRe-enter password for your Keepass file') - confirmation = getpass.getpass(prompt='...' + 'Keepass Password'.rjust(20) + ': ', stream=None) - confirmed = master_password == confirmation - retry_msg = 'The passwords you entered do not match.\nPlease try again.\n' - fail_msg = bcolors.FAIL + bcolors.BOLD + '\nALERT!\n' + retry_msg + bcolors.ENDC - not confirmed and print(fail_msg) + def do_export(self, filename, records, file_password=None, kbdx_key_file=None, **kwargs): + password = file_password or getpass.getpass(prompt='...' + 'Keepass Password'.rjust(20) + ': ', stream=None) or None + if not kbdx_key_file: + print('Press Enter if your Keepass file is not protected with a key file') + keyfile = kbdx_key_file or input('...' + 'Path to Key file'.rjust(20) + ': ') or None + if keyfile: + keyfile = os.path.expanduser(keyfile.strip("\"'")) + else: + keyfile = None sfs = [] # type: list[SharedFolder] rs = [] # type: list[Record] @@ -308,7 +305,8 @@ def do_export(self, filename, records, file_password=None, **kwargs): template_file = os.path.join(os.path.dirname(__file__), 'template.kdbx') with PyKeePass(template_file, password='111111') as kdb: - kdb.password = master_password + kdb.password = password + kdb.keyfile = keyfile root = kdb.root_group for r in rs: From bf0cf0831f320c3b61067dadc0d3cdaf88a9330e Mon Sep 17 00:00:00 2001 From: lthievenaz-keeper Date: Mon, 27 Jul 2026 09:32:35 +0100 Subject: [PATCH 2/3] Updated TOTP field importing The import logic was expecting outdated / incomplete field labels for TOTP. - Kept the old logic in case any Keepass integration still uses them - Added new TOTP fields to trigger the TOTP parsing. - Added TOTP algorithms --- keepercommander/importer/keepass/keepass.py | 22 +++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/keepercommander/importer/keepass/keepass.py b/keepercommander/importer/keepass/keepass.py index b70397d63..0140a9c39 100644 --- a/keepercommander/importer/keepass/keepass.py +++ b/keepercommander/importer/keepass/keepass.py @@ -108,6 +108,7 @@ def do_import(self, filename, **kwargs): totp_issuer = '' totp_period = 0 totp_digits = 0 + totp_algo = None record = Record() fol = Folder() fol.path = folder @@ -145,25 +146,36 @@ def do_import(self, filename, **kwargs): else: field_type = '' field_label = key - if field_label in ('TOTPSecret', 'TOTPPeriod', 'TOTPDigits', 'TOTPIssuer', 'ModifyTOTPSettings', 'ViewTOTPSettings'): + if field_label in ( + 'TimeOtp-Secret-Base32', 'TOTPSecret', + 'TimeOtp-Period', 'TOTPPeriod', + 'TimeOtp-Length', 'TOTPDigits', + 'TimeOtp-Algorithm', + 'TOTPIssuer', + 'ModifyTOTPSettings', 'ViewTOTPSettings' + ): # Ignore TOTP custom fields (set via previous Keeper -> KDBX export implementation) # if the entry's "otp" field contains the corresponding URI if entry.otp: continue - if field_label == 'TOTPSecret': + if field_label in ('TimeOtp-Secret-Base32','TOTPSecret'): totp_secret = value elif field_label == 'TOTPIssuer': totp_issuer = value - elif field_label == 'TOTPPeriod': + elif field_label in ('TimeOtp-Period','TOTPPeriod'): try: totp_period = int(value) except: pass - elif field_label == 'TOTPDigits': + elif field_label in ('TimeOtp-Length','TOTPDigits'): try: totp_digits = int(value) except: pass + elif field_label == 'TimeOtp-Algorithm': + parsed_algo = value.replace('-','')[4:] + if parsed_algo in ('SHA1','SHA256','SHA512'): + totp_algo = parsed_algo else: field = RecordField() field.type = field_type @@ -178,6 +190,8 @@ def do_import(self, filename, **kwargs): value += f'&period={totp_period}' if totp_digits > 0: value += f'&digits={totp_digits}' + if totp_algo: + value += f'&algorithm={totp_algo}' field = RecordField() field.type = 'oneTimeCode' field.value = KeepassImporter.import_field(field_type, value) From 605d6901280517cbea13c30f755f123f74901cfa Mon Sep 17 00:00:00 2001 From: lthievenaz-keeper Date: Mon, 27 Jul 2026 10:01:31 +0100 Subject: [PATCH 3/3] Fix TOTP field exporting Set new TOTP fields for Keepass export, so that it complies with Keepass TOTP definitions. Added Algorithm field --- keepercommander/importer/keepass/keepass.py | 24 ++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/keepercommander/importer/keepass/keepass.py b/keepercommander/importer/keepass/keepass.py index 0140a9c39..32bd27cef 100644 --- a/keepercommander/importer/keepass/keepass.py +++ b/keepercommander/importer/keepass/keepass.py @@ -368,11 +368,25 @@ def do_export(self, filename, records, file_password=None, kbdx_key_file=None, * entry.otp = otp_value # Set custom fields for Pleasant Password TOTP compatibility totp_props = parse_totp_uri(otp_value) - for key in ['secret', 'period', 'issuer', 'digits']: - val = totp_props.get(key) - val and entry.set_custom_property( - f'TOTP{key.capitalize()}', - self.to_keepass_value(val) + totp_props.get('secret') and entry.set_custom_property( + 'TimeOtp-Secret-Base32', + self.to_keepass_value(totp_props['secret']) + ) + totp_props.get('issuer') and entry.set_custom_property( + 'TOTPIssuer', + self.to_keepass_value(totp_props['issuer']) + ) + totp_props.get('digits') and entry.set_custom_property( + 'TimeOtp-Length', + self.to_keepass_value(totp_props['digits']) + ) + totp_props.get('period') and entry.set_custom_property( + 'TimeOtp-Period', + self.to_keepass_value(totp_props['period']) + ) + totp_props.get('algorithm') and entry.set_custom_property( + 'TimeOtp-Algorithm', + f'HMAC-SHA-{totp_props["algorithm"][3:]}' ) continue