Last active
August 29, 2015 14:03
-
-
Save firecat53/fffab123019a12e4fb66 to your computer and use it in GitHub Desktop.
Import keepass 1.x password database into GNU pass (no GUI required)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python | |
"""Import keepass 1.x compatible password database into GNU pass | |
http://www.passwordstore.org/. | |
Uses the kppy (https://pypi.python.org/pypi/kppy) library to open and decode | |
the database. Python 2.7+ and Python 3.x compatible. Best results with Python3 | |
for any unicode issues. | |
Usage: keepass2pass.py <keepass db> [--keyfile <key file if necessary>] | |
Based on http://git.zx2c4.com/password-store/tree/contrib/importers/keepassx2pass.py | |
keepassx2pass.py: Juhamatti Niemelä <[email protected]> | |
This file is licensed under the GPLv2+. | |
keepass_kppy_pass.py: Copyright (C) 2014 Scott Hansen <[email protected]> | |
""" | |
import argparse | |
import os.path | |
import re | |
from getpass import getpass | |
from subprocess import Popen, PIPE | |
from kppy.database import KPDBv1 | |
from kppy.exceptions import KPError | |
def open_db(fn, pw, key=None, ro=True): | |
"""Open and load the database. | |
""" | |
db = KPDBv1(fn, pw, key, ro) | |
db.load() | |
return db | |
def clean_title(title): | |
"""Make the title more command line friendly. | |
""" | |
title = re.sub("(\\|\||\(|\)|/)", "-", title) | |
title = re.sub("-$", "", title) | |
title = re.sub("\@", "At", title) | |
title = re.sub("'", "", title) | |
return title | |
def path_for(title, path=''): | |
""" Generate path name from entry title and current path. | |
""" | |
title = clean_title(title) | |
return os.path.join(path, title) | |
def password_data(entry): | |
"""Return password data and additional info if available from | |
password entry element. | |
""" | |
ret = [] | |
for field in ['password', 'username', 'url', 'comment']: | |
ret.append(getattr(entry, field)) | |
return "\n".join(ret) | |
def import_group(group, path=''): | |
"""Import all entries and sub-groups from given group. | |
""" | |
npath = path_for(group.title, path) | |
for child_group in group.children: | |
import_group(child_group, npath) | |
for entry in group.entries: | |
import_entry(entry, npath) | |
def import_entry(entry, path=''): | |
"""Import new password entry to password-store using pass insert | |
command. | |
""" | |
print("Importing: {}".format(entry.title)) | |
try: | |
data = password_data(entry).encode() | |
except UnicodeEncodeError: | |
try: | |
data = password_data(entry).encode('latin1') | |
except UnicodeEncodeError: | |
print("Unicode error. Unable to import {}".format(entry.title)) | |
return | |
proc = Popen(['pass', 'insert', '--multiline', '--force', | |
path_for(entry.title, path)], stdin=PIPE, stdout=PIPE) | |
proc.communicate(data) | |
proc.wait() | |
def parse_args(): | |
"""Command line arguments. | |
Returns: db-file: path to database file | |
key-file: path to key file, if necessary | |
""" | |
parser = argparse.ArgumentParser() | |
parser.add_argument('dbfile') | |
parser.add_argument('--keyfile') | |
args = parser.parse_args() | |
key = args.keyfile or None | |
return args.dbfile, key | |
def main(): | |
fn, key = parse_args() | |
pw = getpass('Database passphrase: ') | |
try: | |
db = open_db(fn, pw, key) | |
except KPError as e: | |
print("{}".format(e)) | |
else: | |
for group in db.root_group.children: | |
import_group(group) | |
db.close() | |
if __name__ == '__main__': | |
main() |
Thanks for this. I've made some modifications, like improving the clean up of titles for file name creation and also have added titles like Username:
, Title:
, and URL:
for additional password entry data. The modified script is here: https://gist.github.com/dames57/b3e0324cd78559744f0a
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, great script. I have two questions:
I have trouble using the Firefox "Passff" addon and how it matches imported data of this script. Instead of using the keepass user/loginname, Passff uses the name of the generated entry of this script. I'm not sure if this is a passff issue or related in which order this script imports data?! Which leads to my second question.
Could you extend the script in a way, that the "import matching rules" can be easily adapted via variables? This way the user could easily create it's own matching rules.
Btw. where can I find an overview of the Keepass database entries for creating matching rules?