-
-
Save ignlg/0a349712a973cb94be67c8c4a3e3a196 to your computer and use it in GitHub Desktop.
Export Bitwarden to KeePass 2 XML format, with custom banned fields to clean things up
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
| #!/usr/bin/python | |
| from __future__ import print_function | |
| import xmltodict | |
| import json | |
| import sys | |
| import commands | |
| import uuid | |
| import base64 | |
| def get_uuid(name): | |
| name = name.encode('ascii', 'ignore') | |
| uid = uuid.uuid5(uuid.NAMESPACE_DNS, name) | |
| return base64.b64encode(uid.bytes) | |
| def get_folder(f): | |
| return dict(UUID=get_uuid(f['name']), | |
| Name=f['name']) | |
| def get_protected_value(v): | |
| return {'#text': v, '@ProtectInMemory': 'True'} | |
| def get_entry(e): | |
| fields = [] | |
| if 'fields' in e: | |
| for f in e['fields']: | |
| if f['name'] is not None: | |
| fields.append(dict(Key=f['name'], | |
| Value=get_protected_value(f['value']))) | |
| urls = '' | |
| username, password = '', '' | |
| notes = e['notes'] if e['notes'] is not None else '' | |
| if 'login' in e: | |
| if 'uris' in e['login']: | |
| urls = [u['uri'] for u in e['login']['uris']] | |
| urls = ','.join(urls) | |
| username = e['login']['username'] | |
| password = e['login']['password'] | |
| username = username or '' | |
| password = password or '' | |
| entry = dict(UUID=get_uuid(e['name']), | |
| String=[dict(Key='Title', Value=e['name']), | |
| dict(Key='UserName', Value=username), | |
| dict(Key='Password', Value=get_protected_value(password)), | |
| dict(Key='URL', Value=urls), | |
| dict(Key='Notes', Value=notes) | |
| ] + fields) | |
| # print(entry) | |
| return entry | |
| def main(): | |
| # get output from bw | |
| cmd = "(bw list folders | jq '{folders: .}' ; bw list items | jq '{items: .}') | cat | jq -s '. | add'" | |
| status, output = commands.getstatusoutput(cmd) | |
| # read stdin | |
| # stdin = sys.stdin.read() | |
| if status != 0: | |
| print("Error ...") | |
| print(output) | |
| sys.exit(1) | |
| # parse input to json | |
| d = json.loads(output) | |
| #print(d['folders'][0]['name']) | |
| # print(d['items'][0]) | |
| # print(get_entry(d['items'][0])) | |
| entries = [get_entry(e) for e in d['items']] | |
| # Meta element | |
| meta = dict() | |
| # loop over folders | |
| in_folders = d['folders'] | |
| out_folders = [] | |
| for f in in_folders: | |
| folder = get_folder(f) | |
| folder_id = f['id'] | |
| # loop on entries in this folder | |
| out_entries = [] | |
| for entry, item in zip(entries, d['items']): | |
| if item['folderId'] == folder_id: | |
| out_entries.append(entry) | |
| # add if there is something | |
| if len(out_entries) > 0: | |
| folder['Entry'] = out_entries | |
| # add to output | |
| out_folders.append(folder) | |
| # Root group | |
| root_group = get_folder(dict(name='Root')) | |
| root_group['Group'] = out_folders | |
| # root_group['Entry'] = entries | |
| # Root element | |
| root=dict(Group=root_group) | |
| # xml contents | |
| xml = dict(KeePassFile=dict(Meta=meta, Root=root)) | |
| print(xmltodict.unparse(xml, pretty=True)) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment