Skip to content

Instantly share code, notes, and snippets.

@jleclanche
Last active December 8, 2022 04:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jleclanche/671f3bfcd0ae0d42dc9aab94df0d60bc to your computer and use it in GitHub Desktop.
Save jleclanche/671f3bfcd0ae0d42dc9aab94df0d60bc to your computer and use it in GitHub Desktop.
Use PyKeePass to turn a kdbx file into a 1Password-importable csv file
# License: CC0 (https://creativecommons.org/share-your-work/public-domain/cc0/)
import csv
import getpass
import sys
from pykeepass import PyKeePass
class BadData(Exception):
pass
def parse_kdbx(filename: str, password: str) -> list:
ret = []
kp = PyKeePass(filename, password=password)
for entry in kp.entries:
totp_seed = entry._get_string_field("TOTP Seed") or ""
totp_settings = entry._get_string_field("TOTP Settings") or ""
if totp_seed:
seconds, digits = totp_settings.split(";")
if digits == "S":
sys.stderr.write("WARNING: Steam TOTP not supported!!\n")
elif not digits.isdigit():
raise BadData(f"digits = {repr(digits)} for TOTP settings is invalid")
if digits != "6":
totp_settings += f"&digits={digits}"
ret.append((
entry.title or "",
entry.url or "",
entry.username or "",
entry.password or "",
entry.notes or "",
totp_seed or "",
))
return ret
def main():
filenames = sys.argv[1:]
passwords = {}
rows = []
for filename in filenames:
if filename in passwords:
continue
password = getpass.getpass(f"Please enter the passphrase for {repr(filename)}: ")
rows += parse_kdbx(filename, password)
out = "out.csv"
with open(out, "w") as f:
writer = csv.writer(f, lineterminator="\n")
writer.writerows(rows)
print(f"Written {len(rows)} passwords to {out}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment