class PyKeePassTests3(KDBX3Tests): def setUp(self): shutil.copy(os.path.join(base_dir, self.database), os.path.join(base_dir, 'change_creds.kdbx')) self.kp = PyKeePass(os.path.join(base_dir, self.database), password=self.password, keyfile=os.path.join(base_dir, self.keyfile)) self.kp_tmp = PyKeePass(os.path.join(base_dir, 'change_creds.kdbx'), password=self.password, keyfile=os.path.join(base_dir, self.keyfile)) def test_set_credentials(self): self.kp_tmp.password = '******' self.kp_tmp.keyfile = os.path.join(base_dir, 'change.key') self.kp_tmp.save() self.kp_tmp = PyKeePass(os.path.join(base_dir, 'change_creds.kdbx'), password='******', keyfile=os.path.join(base_dir, 'change.key')) results = self.kp.find_entries_by_username('foobar_user', first=True) self.assertEqual('foobar_user', results.username) def test_dump_xml(self): self.kp.dump_xml('db_dump.xml') with open('db_dump.xml') as f: first_line = f.readline() self.assertEqual( first_line, '<?xml version=\'1.0\' encoding=\'utf-8\' standalone=\'yes\'?>\n' ) def tearDown(self): os.remove(os.path.join(base_dir, 'change_creds.kdbx'))
def process(): args = get_args() print(args.dbpath) if not os.path.exists(args.dbpath): print("DB file path does not exist", file=sys.stderr) sys.exit(1) password = getpass() try: db = KP(args.dbpath, password=password) except FileNotFoundError: print("DB file path does not exist", file=sys.stderr) sys.exit(1) except OSError: print("Invalid password", file=sys.stderr) sys.exit(1) except Exception: print("Misc error", file=sys.stderr) sys.exit(1) groups = db.find_groups_by_name(args.groupname) if len(groups) == 0: print("Group does not exist", file=sys.stderr) sys.exit(1) if len(groups) > 1: print("Multiple groups of that name exist. Cannot continue", file=sys.stderr) sys.exit(1) group = groups[0] ## Only grab from root path = "".join([db.root_group.path, args.entrytitle]) entries = db.find_entries_by_path(path) if len(entries) == 0: print("Entry by that title does not exist", file=sys.stderr) for entry in entries: group.append(entry) db.save()
class AttachmentTests3(KDBX3Tests): # get some things ready before testing def setUp(self): shutil.copy( os.path.join(base_dir, self.database), os.path.join(base_dir, 'test_attachment.kdbx') ) self.open() def open(self): self.kp = PyKeePass( os.path.join(base_dir, 'test_attachment.kdbx'), password=self.password, keyfile=os.path.join(base_dir, self.keyfile) ) def test_create_delete_binary(self): with self.assertRaises(BinaryError): self.kp.delete_binary(999) with self.assertRaises(BinaryError): e = self.kp.entries[0] e.add_attachment(filename='foo.txt', id=123) e.attachments[0].binary binary_id = self.kp.add_binary(b'Ronald McDonald Trump') self.kp.save() self.open() self.assertEqual(self.kp.binaries[binary_id], b'Ronald McDonald Trump') self.assertEqual(len(self.kp.attachments), 1) num_attach = len(self.kp.binaries) self.kp.delete_binary(binary_id) self.kp.save() self.open() self.assertEqual(len(self.kp.binaries), num_attach - 1) def test_attachment_reference_decrement(self): e = self.kp.entries[0] binary_id1 = self.kp.add_binary(b'foobar') binary_id2 = self.kp.add_binary(b'foobar2') attachment1 = e.add_attachment(binary_id1, 'foo.txt') attachment2 = e.add_attachment(binary_id2, 'foo.txt') self.kp.delete_binary(binary_id1) self.assertEqual(attachment2.id, binary_id2 - 1) def test_fields(self): e = self.kp.entries[0] binary_id = self.kp.add_binary(b'foobar') a = e.add_attachment(filename='test.txt', id=binary_id) self.assertEqual(a.data, b'foobar') self.assertEqual(a.id, binary_id) self.assertEqual(a.filename, 'test.txt') def tearDown(self): os.remove(os.path.join(base_dir, 'test_attachment.kdbx'))
def main(fullpath): password = getpass() try: db = PyKeePass(fullpath, password) except: print("Wrong path/password") return for entry in db.entries: entry.title = parse(entry.url) db.save()
class AlteraKeePass: def __init__(self, arquivo_kdbx, senha): self.kp = PyKeePass(arquivo_kdbx, password=senha) def cria_entrada(self, grupo, nome, usuario, senha, url, icone, ip, porta): self.group = self.kp.find_groups(name=grupo, first=True) self.kp.add_entry(self.group, nome, usuario, senha, url=url, icon=icone) self.entry = self.kp.find_entries(title=nome, first=True) self.entry.set_custom_property('IP', ip) self.entry.set_custom_property('PORT', porta) self.kp.save()
def write_entry(kdbx_file, kdbx_password, group_path, entry_title, entry_username, entry_password, entry_url, entry_notes, entry_tags, kdbx_keyfile=None, force_creation=False, outfile=None): logging.info('Attempt to write entry "{}: {}:{}" to {}'.format( entry_title, entry_username, entry_password, group_path)) samba_db = False if kdbx_file.startswith('smb://'): samba_db = True smb_kdbx_file = smb_retrieve(kdbx_file) kp = PyKeePass(smb_kdbx_file if samba_db else kdbx_file, password=kdbx_password, keyfile=kdbx_keyfile) dest_group = kp.find_groups_by_path(group_path, first=True) kp.add_entry(destination_group=dest_group, title=entry_title, username=entry_username, password=entry_password, url=entry_url, notes=entry_notes, tags=entry_tags, force_creation=force_creation) if outfile: if outfile.startswith('smb://'): file_written = kp.save() smb_send(file_written.name, outfile) logging.info('Sent database file to {}'.format(outfile)) else: file_written = kp.save(kdbx_file) logging.info('KeePass DB written to {}'.format(file_written.name)) else: if samba_db: file_written = kp.save() smb_send(file_written.name, kdbx_file) logging.info('Sent database file to {}'.format(kdbx_file)) else: file_written = kp.save(kdbx_file) logging.info('KeePass DB written to {}'.format(file_written.name))
def keepassxc_new_ssh_key(db_name, db_password, passphrase="", title="", username=""): kp = PyKeePass(db_name, password=db_password) keyname = title.replace(" ", "-") ssh_group = kp.find_groups(name="SSH", first=True) if not ssh_group: ssh_group = kp.add_group(kp.root_group, "SSH") entry = kp.add_entry(ssh_group, title=title, username=username, password=passphrase) pub, priv = generate_keys(passphrase) entry_ssh_settings = f"""\ <?xml version="1.0" encoding="UTF-16"?> <EntrySettings xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <AllowUseOfSshKey>true</AllowUseOfSshKey> <AddAtDatabaseOpen>true</AddAtDatabaseOpen> <RemoveAtDatabaseClose>true</RemoveAtDatabaseClose> <UseConfirmConstraintWhenAdding>false</UseConfirmConstraintWhenAdding> <UseLifetimeConstraintWhenAdding>true</UseLifetimeConstraintWhenAdding> <LifetimeConstraintDuration>600</LifetimeConstraintDuration> <Location> <SelectedType>attachment</SelectedType> <AttachmentName>{keyname}</AttachmentName> <SaveAttachmentToTempFile>false</SaveAttachmentToTempFile> </Location> </EntrySettings>""" attach(kp, entry, "KeeAgent.settings", entry_ssh_settings, encoding="utf-16") attach(kp, entry, keyname, priv) attach(kp, entry, f"{keyname}.pub", pub) kp.save()
class AttachmentTests3(KDBX3Tests): # get some things ready before testing def setUp(self): shutil.copy( os.path.join(base_dir, self.database), os.path.join(base_dir, 'test_attachment.kdbx') ) self.open() def open(self): self.kp = PyKeePass( os.path.join(base_dir, 'test_attachment.kdbx'), password=self.password, keyfile=os.path.join(base_dir, self.keyfile) ) def test_create_delete_attachment(self): attachment_id = self.kp.add_binary(b'Ronald McDonald Trump') self.kp.save() self.open() self.assertEqual(self.kp.binaries[-1], b'Ronald McDonald Trump') num_attach = len(self.kp.binaries) self.kp.delete_binary(len(self.kp.binaries) - 1) self.kp.save() self.open() self.assertEqual(len(self.kp.binaries), num_attach - 1) def test_attachment_reference_decrement(self): e = self.kp.entries[0] attachment_id1 = self.kp.add_binary(b'foobar') attachment_id2 = self.kp.add_binary(b'foobar2') attachment1 = e.add_attachment(attachment_id1, 'foo.txt') attachment2 = e.add_attachment(attachment_id2, 'foo.txt') self.kp.delete_binary(attachment_id1) self.assertEqual(attachment2.id, attachment_id2 - 1) def tearDown(self): os.remove(os.path.join(base_dir, 'test_attachment.kdbx'))
def merge_two_databases(self, file_database1, file_database2, file_output_database, master_password, debug=False): try: kp1 = PyKeePass(file_database1, password=master_password) except CredentialsError: raise DB1WrongPasswordError try: kp2 = PyKeePass(file_database2, password=master_password) except CredentialsError: raise DB2WrongPasswordError self.sync_groups(kp2,kp1, debug=debug) self.sync_entries(kp2,kp1, debug=debug) self.sync_custom_properties(kp2,kp1, debug=debug) kp1.save(filename=file_output_database)
class KeePass(): def __init__(self, password, cfgfile='pass.yml'): keyfile = None with open(cfgfile, 'r') as ymlfile: cfg = yaml.safe_load(ymlfile)['keepass'] dbfile = cfg['dbfile'] group = cfg['group'] newentry = cfg['newentry'] oldentry = cfg['oldentry'] if 'keyfile' in cfg: keyfile = cfg['keyfile'] self.keepass = PyKeePass(dbfile, password=password, keyfile=keyfile) self.group = self.keepass.find_groups(name=group, first=True) self.oldentry = self.keepass.find_entries( title=oldentry, group=self.group, first=True) self.newentry = self.keepass.find_entries( title=newentry, group=self.group, first=True) def get_hosts(self): hosts = self.oldentry.url ret = [] for host in re.split(r'\s+', hosts): ret.append(Host( host, self.newentry.username, self.oldentry.password, self.newentry.password)) return ret def add_to_url(self, host): if self.newentry.url is not None: self.newentry.url = self.newentry.url + ' ' + host else: self.newentry.url = host self.keepass.save() def add_to_notes(self, msg): if self.newentry.notes is not None: self.newentry.notes = self.newentry.notes + '\n' + msg else: self.newentry.notes = msg self.keepass.save()
class KDBX(Formatter, PasswordImporter, PasswordExporter): """Base class for KDBX based importer & exporter. The importer supports binary attachments. It requires PyKeePass to run. :param PyKeePass keepass: The keepass repository to work on. :param list attributes: List of the attributes of PyKeePass to import. """ cap = Cap.FORMAT | Cap.IMPORT | Cap.EXPORT name = 'keepass' format = 'kdbx' magic = b'\x03\xd9\xa2\x9a' keys = {'login': '******', 'comments': 'notes', 'group': 'path'} attributes = { 'title', 'username', 'password', 'url', 'notes', 'icon', 'tags', 'autotype_enabled', 'autotype_sequence', 'path', 'is_a_history_entry' } def __init__(self, prefix=None, settings=None): self.keepass = None settings = {} if settings is None else settings keyfile = settings.get('key', '') self.keyfile = None if keyfile == '' else keyfile super(KDBX, self).__init__(prefix, settings) # Import methods def _getentry(self, kpentry): entry = dict() keys = self.invkeys() for attr in self.attributes: if hasattr(kpentry, attr): entry[keys.get(attr, attr)] = getattr(kpentry, attr) for key, value in kpentry.custom_properties.items(): entry[key] = value return entry def parse(self): """Parse Keepass KDBX3 and KDBX4 files.""" for kpentry in self.keepass.entries: if self.root not in kpentry.path: continue entry = self._getentry(kpentry) entry['group'] = os.path.dirname(entry['group']) if kpentry.history: for hentry in kpentry.history: history = self._getentry(hentry) history['group'] = os.path.join('History', entry['group']) self.data.append(history) for att in kpentry.attachments: attachment = dict() attachment['group'] = entry['group'] attachment['title'] = att.filename attachment['data'] = att.data self.data.append(attachment) if entry.get('attachments', None): entry['attachments'] += ", %s" % att.filename else: entry['attachments'] = att.filename self.data.append(entry) # Export methods def insert(self, entry): """Insert a password entry into KDBX encrypted vault file.""" ignore = {'password', 'path', 'title', 'group', 'data'} path = os.path.join(self.root, entry.get('path')) title = os.path.basename(path) group = os.path.dirname(path) root_group = self.keepass.root_group kpgroup = self.keepass.find_groups(path=group) if not kpgroup: for grp in group.split('/'): kpgroup = self.keepass.find_groups(path=grp) if not kpgroup: kpgroup = self.keepass.add_group(root_group, grp) root_group = kpgroup if not self.force: pkentry = self.keepass.find_entries(title=title, group=kpgroup, recursive=False, first=True) if pkentry is not None: raise PMError("An entry already exists for %s." % path) kpentry = self.keepass.add_entry(destination_group=kpgroup, title=title, username=entry.pop('login', ''), password=entry.pop('password', ''), url=entry.pop('url', None), notes=entry.pop('comments', None), tags=entry.pop('tags', None), expiry_time=entry.pop( 'expiry_time', None), icon=entry.pop('icon', None), force_creation=True) for key, value in entry.items(): if key in ignore: continue kpentry.set_custom_property(key, str(value)) if 'data' in entry: attid = self.keepass.add_binary(entry['data']) kpentry.add_attachment(attid, title) # Context manager methods def open(self): """Open the keepass repository.""" if not PYKEEPASS: raise ImportError(name='pykeepass') try: self.keepass = PyKeePass(self.prefix, password=getpassword(self.prefix), keyfile=self.keyfile) except CredentialsIntegrityError as error: raise PMError(error) def close(self): """Close the keepass repository.""" self.keepass.save() # Format recognition methods def detecter_open(self): """Enter the tryformat context manager.""" self.file = open(self.prefix, 'rb') def detecter_close(self): """Leave the tryformat context manager.""" self.file.close() def is_format(self): """Return True if the file is a KDBX file.""" sign = self.file.read(4) if sign != self.magic: return False return True def checkheader(self, header, only=False): """No header check.""" return True @classmethod def header(cls): """No header for KDBX file.""" return ''
def bitwarden_to_keepass(args): try: kp = PyKeePass(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except FileNotFoundError: logging.info('KeePass database does not exist, creating a new one.') kp = create_database(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except CredentialsError as e: logging.error(f'Wrong password for KeePass database: {e}') return folders = subprocess.check_output( f'{quote(args.bw_path)} list folders --session {quote(args.bw_session)}', shell=True, encoding='utf8') folders = json.loads(folders) # sort folders so that in the case of nested folders, the parents would be guaranteed to show up before the children folders.sort(key=lambda x: x['name']) groups_by_id = {} groups_by_name = {} for folder in folders: # entries not associated with a folder should go under the root group if folder['id'] is None: groups_by_id[folder['id']] = kp.root_group continue parent_group = kp.root_group target_name = folder['name'] # check if this is a nested folder; set appropriate parent group if so folder_path_split = target_name.rsplit('/', maxsplit=1) if len(folder_path_split) > 1: parent_group = groups_by_name[folder_path_split[0]] target_name = folder_path_split[1] new_group = kp.add_group(parent_group, target_name) groups_by_id[folder['id']] = new_group groups_by_name[folder['name']] = new_group logging.info(f'Folders done ({len(groups_by_id)}).') items = subprocess.check_output( f'{quote(args.bw_path)} list items --session {quote(args.bw_session)}', shell=True, encoding='utf8') items = json.loads(items) logging.info(f'Starting to process {len(items)} items.') for item in items: bw_item = Item(item) is_duplicate_title = False try: while True: entry_title = bw_item.get_name( ) if not is_duplicate_title else '{name} - ({item_id}'.format( name=bw_item.get_name(), item_id=bw_item.get_id()) try: entry = kp.add_entry(destination_group=groups_by_id[ bw_item.get_folder_id()], title=entry_title, username=bw_item.get_username(), password=bw_item.get_password(), notes=bw_item.get_notes()) break except Exception as e: if 'already exists' in str(e): is_duplicate_title = True continue raise totp_secret, totp_settings = bw_item.get_totp() if totp_secret and totp_settings: entry.set_custom_property('TOTP Seed', totp_secret) entry.set_custom_property('TOTP Settings', totp_settings) for uri in bw_item.get_uris(): entry.url = uri['uri'] break # todo append additional uris to notes? for field in bw_item.get_custom_fields(): entry.set_custom_property(field['name'], field['value']) for card_field_name, card_field_val in bw_item.get_card().items(): if card_field_val is not None: entry.set_custom_property( f"{CUSTOM_FIELD_PREFIX}_CARD_{card_field_name}", card_field_val) for identity_field_name, identity_field_val in bw_item.get_identity( ).items(): if identity_field_val is not None: entry.set_custom_property( f"{CUSTOM_FIELD_PREFIX}_IDENTITY_{identity_field_name}", identity_field_val) for attachment in bw_item.get_attachments(): attachment_tmp_path = f'{args.tmp_path}/attachment/{attachment["fileName"]}' attachment_path = subprocess.check_output( f'{quote(args.bw_path)} get attachment' f' --raw {quote(attachment["id"])} ' f'--itemid {quote(bw_item.get_id())} ' f'--output {quote(attachment_tmp_path)} --session {quote(args.bw_session)}', shell=True, encoding='utf8').rstrip() attachment_id = kp.add_binary( open(attachment_path, 'rb').read()) entry.add_attachment(attachment_id, attachment['fileName']) os.remove(attachment_path) except Exception as e: logging.warning( f'Skipping item named "{item["name"]}" because of this error: {repr(e)}' ) continue logging.info('Saving changes to KeePass database.') kp.save() logging.info('Export completed.')
def bitwarden_to_keepass(args): try: kp = PyKeePass(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except FileNotFoundError: logging.info('KeePass database does not exist, creating a new one.') kp = create_database(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except CredentialsError as e: logging.error(f'Wrong password for KeePass database: {e}') return folders = subprocess.check_output( f'{quote(args.bw_path)} list folders --session {quote(args.bw_session)}', shell=True, encoding='utf8') folders = json.loads(folders) groups = {} for folder in folders: groups[folder['id']] = kp.add_group(kp.root_group, folder['name']) logging.info(f'Folders done ({len(groups)}).') items = subprocess.check_output( f'{quote(args.bw_path)} list items --session {quote(args.bw_session)}', shell=True, encoding='utf8') items = json.loads(items) logging.info(f'Starting to process {len(items)} items.') for item in items: if item['type'] in [ItemTypes.CARD, ItemTypes.IDENTITY]: logging.warning( f'Skipping credit card or identity item "{item["name"]}".') continue bw_item = Item(item) is_duplicate_title = False try: while True: entry_title = bw_item.get_name( ) if not is_duplicate_title else '{name} - ({item_id}'.format( name=bw_item.get_name(), item_id=bw_item.get_id()) try: entry = kp.add_entry( destination_group=groups[bw_item.get_folder_id()], title=entry_title, username=bw_item.get_username(), password=bw_item.get_password(), notes=bw_item.get_notes()) break except Exception as e: if 'already exists' in str(e): is_duplicate_title = True continue raise totp_secret, totp_settings = bw_item.get_totp() if totp_secret and totp_settings: entry.set_custom_property('TOTP Seed', totp_secret) entry.set_custom_property('TOTP Settings', totp_settings) for uri in bw_item.get_uris(): entry.url = uri['uri'] break # todo append additional uris to notes? for field in bw_item.get_custom_fields(): entry.set_custom_property(field['name'], field['value']) for attachment in bw_item.get_attachments(): attachment_tmp_path = f'/tmp/attachment/{attachment["fileName"]}' attachment_path = subprocess.check_output( f'{quote(args.bw_path)} get attachment' f' --raw {quote(attachment["id"])} ' f'--itemid {quote(bw_item.get_id())} ' f'--output {quote(attachment_tmp_path)} --session {quote(args.bw_session)}', shell=True, encoding='utf8').rstrip() attachment_id = kp.add_binary( open(attachment_path, 'rb').read()) entry.add_attachment(attachment_id, attachment['fileName']) os.remove(attachment_path) except Exception as e: logging.warning( f'Skipping item named "{item["name"]}" because of this error: {repr(e)}' ) continue logging.info('Saving changes to KeePass database.') kp.save() logging.info('Export completed.')
class KeepassVault(Vault): def __init__(self, vault_configs): validate(vault_configs, "yac/schema/vaults/keepass.json") self.vault_path = search('"vault-path"', vault_configs) vault_pwd_path = search('"vault-pwd-path"', vault_configs) self.vault_pwd = search('"vault-pwd"', vault_configs) if not self.vault_pwd and vault_pwd_path and os.path.exists( vault_pwd_path): self.vault_pwd = read_pwd_file(vault_pwd_path) self.ready = False self.initialized = False self.kp = None def get_type(self): return "keepass" def initialize(self, params): servicefile_path = params.get("servicefile-path", "") # apply intrinsics in the vault password vault_pwd = apply_intrinsics(self.vault_pwd, params) full_path = os.path.join(servicefile_path, self.vault_path) if os.path.exists(full_path): if vault_pwd: try: self.kp = PyKeePass(full_path, password=vault_pwd) self.ready = True self.initialized = True except IOError as e: print(e) else: self.ready = False self.initialized = False def is_ready(self): return self.ready def is_initialized(self): return self.initialized def get(self, lookup_config): entry = self.kp.find_entries(path=lookup_config['path'], first=True) value = "" if entry: field_name = lookup_config['field'] if field_name == "password": value = entry.password elif field_name == "username": value = entry.username elif field_name == "url": value = entry.url elif field_name == "notes": value = entry.notes return value def set(self, lookup_config, secret_value): entry = self.kp.find_entries(path=lookup_config['path'], first=True) if entry: field_name = lookup_config['field'] if field_name == "password": entry.password = secret_value elif field_name == "username": entry.username = secret_value elif field_name == "url": entry.url = secret_value self.kp.save() def load_cached_vault_path(self): vault_path = get_cache_value('keepass-vault-path') if not vault_path: vault_path = string_validation( "KeePass Vault Path", "Path to the KeePass vault file for secrets lookup", [], path_validation, True) set_cache_value_ms('keepass-vault-path', vault_path) return vault_path def load_cached_vault_pwd(self): vault_pwd = get_cache_value('keepass-vault-pwd') if not vault_pwd: vault_pwd = string_validation( "KeePass Vault Password", "The master key for the KeePass vault", [], string_validation, True, 100, True) set_cache_value_ms('keepass-vault-pwd', vault_pwd) return vault_pwd def clear_secrets_cache(self): set_cache_value_ms('keepass-vault-path', "") set_cache_value_ms('keepass-vault-pwd', "")
def bitwarden_to_keepass(args): try: kp = PyKeePass(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except FileNotFoundError: logging.info('KeePass database does not exist, creating a new one.') kp = create_database(args.database_path, password=args.database_password, keyfile=args.database_keyfile) except CredentialsIntegrityError as e: logging.error(f'Wrong password for KeePass database: {e}') return folders = subprocess.check_output( f'{quote(args.bw_path)} list folders --session {quote(args.bw_session)}', shell=True, encoding='utf8') folders = json.loads(folders) groups = {} for folder in folders: groups[folder['id']] = kp.add_group(kp.root_group, folder['name']) logging.info(f'Folders done ({len(groups)}).') items = subprocess.check_output( f'{quote(args.bw_path)} list items --session {quote(args.bw_session)}', shell=True, encoding='utf8') items = json.loads(items) logging.info(f'Starting to process {len(items)} items.') for item in items: if item['type'] == ItemTypes.CARD: logging.warning(f'Skipping credit card item "{item["name"]}".') continue bw_item = Item(item) e = kp.add_entry(groups[bw_item.get_folder_id()], title=bw_item.get_name(), username=bw_item.get_username(), password=bw_item.get_password(), notes=bw_item.get_notes()) totp_secret, totp_settings = bw_item.get_totp() if totp_secret and totp_settings: e.set_custom_property('TOTP Seed', totp_secret) e.set_custom_property('TOTP Settings', totp_settings) for uri in bw_item.get_uris(): e.url = uri['uri'] break # todo append additional uris to notes? for field in bw_item.get_custom_fields(): e.set_custom_property(str(field['name']), field['value']) for attachment in bw_item.get_attachments(): attachment_tmp_path = f'./attachment_tmp/{attachment["fileName"]}' attachment_path = subprocess.check_output( f'{quote(args.bw_path)} get attachment' f' --raw {quote(attachment["id"])} ' f'--itemid {quote(bw_item.get_id())} ' f'--output {quote(attachment_tmp_path)} --session {quote(args.bw_session)}', shell=True, encoding='utf8').rstrip() attachment_id = kp.add_binary(open(attachment_path, 'rb').read()) e.add_attachment(attachment_id, attachment['fileName']) os.remove(attachment_path) logging.info('Saving changes to KeePass database.') kp.save() logging.info('Export completed.')
def test_open_save(self): """try to open all databases, save them, then open the result""" with open(os.path.join(base_dir, 'test3.kdbx'), 'rb') as file: stream = BytesIO(file.read()) filenames_in = [ os.path.join(base_dir, 'test3.kdbx'), # KDBX v3 test os.path.join(base_dir, 'test4.kdbx'), # KDBX v4 test os.path.join(base_dir, 'test4_aes.kdbx'), # KDBX v4 AES test os.path.join(base_dir, 'test4_aeskdf.kdbx'), # KDBX v3 AESKDF test os.path.join(base_dir, 'test4_chacha20.kdbx'), # KDBX v4 ChaCha test os.path.join(base_dir, 'test4_twofish.kdbx'), # KDBX v4 Twofish test os.path.join(base_dir, 'test4_hex.kdbx'), # legacy 64 byte hexadecimal keyfile test os.path.join(base_dir, 'test3.kdbx'), # KDBX v3 transformed_key open test os.path.join(base_dir, 'test4_hex.kdbx'), # KDBX v4 transformed_key open test stream, os.path.join(base_dir, 'test4_aes_uncompressed.kdbx') # KDBX v4 AES uncompressed test ] filenames_out = [ os.path.join(base_dir, 'test3.kdbx.out'), os.path.join(base_dir, 'test4.kdbx.out'), os.path.join(base_dir, 'test4_aes.kdbx.out'), os.path.join(base_dir, 'test4_aeskdf.kdbx.out'), os.path.join(base_dir, 'test4_chacha20.kdbx.out'), os.path.join(base_dir, 'test4_twofish.kdbx.out'), os.path.join(base_dir, 'test4_hex.kdbx.out'), os.path.join(base_dir, 'test3.kdbx.out'), os.path.join(base_dir, 'test4_hex.kdbx.out'), BytesIO(), os.path.join(base_dir, 'test4_aes_uncompressed.kdbx.out'), os.path.join(base_dir, 'test4_twofish_uncompressed.kdbx.out'), os.path.join(base_dir, 'test4_chacha20_uncompressed.kdbx.out'), ] passwords = [ 'password', 'password', 'password', 'password', 'password', 'password', 'password', None, None, 'password', 'password', 'password', 'password', ] transformed_keys = [ None, None, None, None, None, None, None, b'\xfb\xb1!\x0e0\x94\xd4\x868\xa5\x04\xe6T\x9b<\xf9+\xb8\x82EN\xbc\xbe\xbc\xc8\xd3\xbbf\xfb\xde\xff.', b'M\xb7\x08\xf6\xa7\xd1v\xb1{&\x06\x8f\xae\xe9\r\xeb\x9a\x1b\x02b\xce\xf2\x8aR\xaea)7\x1fs\xe9\xc0', None, None, None, None, ] keyfiles = [ 'test3.key', 'test4.key', 'test4.key', 'test4.key', 'test4.key', 'test4.key', 'test4_hex.key', None, None, 'test3.key', None, None, None, ] encryption_algorithms = [ 'aes256', 'chacha20', 'aes256', 'aes256', 'chacha20', 'twofish', 'chacha20', 'aes256', 'chacha20', 'aes256', 'aes256', 'twofish', 'chacha20', ] kdf_algorithms = [ 'aeskdf', 'argon2', 'argon2', 'aeskdf', 'argon2', 'argon2', 'argon2', 'aeskdf', 'argon2', 'aeskdf', 'argon2', 'argon2', 'argon2', ] versions = [ (3, 1), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (4, 0), (3, 1), (4, 0), (3, 1), (4, 0), (4, 0), (4, 0), ] for (filename_in, filename_out, password, transformed_key, keyfile, encryption_algorithm, kdf_algorithm, version) in zip( filenames_in, filenames_out, passwords, transformed_keys, keyfiles, encryption_algorithms, kdf_algorithms, versions ): kp = PyKeePass( filename_in, password, None if keyfile is None else os.path.join(base_dir, keyfile), transformed_key=transformed_key ) self.assertEqual(kp.encryption_algorithm, encryption_algorithm) self.assertEqual(kp.kdf_algorithm, kdf_algorithm) self.assertEqual(kp.version, version) kp.save( filename_out, transformed_key=transformed_key ) if hasattr(filename_out, "seek"): # rewind so PyKeePass can read from the same stream filename_out.seek(0) kp = PyKeePass( filename_out, password, None if keyfile is None else os.path.join(base_dir, keyfile), transformed_key=transformed_key ) for filename in os.listdir(base_dir): if filename.endswith('.out'): os.remove(os.path.join(base_dir, filename))
print("Modo de empleo: {} <input.geco>") sys.exit() input_file = sys.argv[1] output_file = 'db.kdbx' master = getpass.getpass("Contraseña maestra: ") aes = CustomAES() kp = PyKeePass('database.kdbx', password="******") kp.password = master groups = {} with open(input_file, 'r') as f: for p in f.readlines(): name, type, description, account, password, cypher_method, updated, expiration = p.split("|") expiration = datetime.datetime.fromtimestamp(float(expiration)) updated = datetime.datetime.fromtimestamp(float(updated)) name, type, description, account =\ map(lambda x: x.replace("\\n", "\n"), (name, type, description, account)) clear = aes.decrypt(password, master) if type and not type in groups: group = kp.add_group(kp.root_group, type) groups[type] = group group = groups.get(type, kp.root_group) kp.add_entry(group, name, account, clear, notes=description) kp.save(output_file)
class P2KP2: """Convert a Pass db into a Keepass2 one.""" db: PyKeePass def __init__(self, password: str, destination: str = None, overwrite: bool = False): """Constructor for P2KP2 :param password: the password for the new Keepass db :param destination: the final db path :param overwrite: force writing over existing database """ if destination is None: destination = "pass.kdbx" if not os.path.exists(destination) or overwrite: copyfile(empty_db_path, destination) else: raise DbAlreadyExistsException() self.db = PyKeePass(destination) self.db.password = password self.db.save() self.event_stream = Subject() def populate_db(self, pass_reader: PassReader): """Populate the keepass db with data from the PassReader.""" i = 0 for pass_entry in pass_reader.entries: self.add_entry(pass_entry) i = i + 1 self.event_stream.on_next(i) self.db.save() def add_entry(self, pass_entry: PassEntry) -> Entry: """Add a keepass entry to the db containing all data from the relative pass entry. Create the group if needed. :param pass_entry: the original pass entry :return: the newly added keepass entry """ # find the correct group for the entry. If not there, create it entry_group = self.db.root_group # start from the root group if len(pass_entry.groups) > 0: for group_name in pass_entry.groups: # since pass folder names are unique, the possible first result is also the only one group = self.db.find_groups(name=group_name, recursive=False, group=entry_group, first=True) if group is None: # the group is not already there, let's create it group = self.db.add_group(destination_group=entry_group, group_name=group_name) entry_group = group # create the entry, setting group, title, user and pass entry = self.db.add_entry(entry_group, pass_entry.title, pass_entry.user, pass_entry.password) # set the url and the notes entry.url = pass_entry.url entry.notes = pass_entry.notes # add all custom fields for pass_entry, value in pass_entry.custom_properties.items(): entry.set_custom_property(pass_entry, value) return entry
class KDBX(Formatter, PasswordImporter, PasswordExporter): """Base class for KDBX based importer & exporter. The importer supports binary attachments. It requires PyKeePass to run. :param PyKeePass keepass: The keepass repository to work on. :param list attributes: List of the attributes of PyKeePass to import. """ cap = Cap.FORMAT | Cap.IMPORT | Cap.EXPORT name = 'keepass' format = 'kdbx' magic = b'\x03\xd9\xa2\x9a' keys = {'login': '******', 'comments': 'notes', 'group': 'path'} attributes = { 'title', 'username', 'password', 'url', 'notes', 'icon', 'tags', 'autotype_enabled', 'autotype_sequence', 'is_a_history_entry' } reference = re.compile(r'\{REF:([A-Z])@I:([0-9A-F]{32})\}') def __init__(self, prefix=None, settings=None): self.keepass = None settings = {} if settings is None else settings keyfile = settings.get('key', '') self.keyfile = None if keyfile == '' else keyfile super(KDBX, self).__init__(prefix, settings) # Import methods def _getentry(self, kpentry): entry = dict() entry['group'] = os.sep.join(kpentry.path) keys = self.invkeys() for attr in self.attributes: if hasattr(kpentry, attr): value = getattr(kpentry, attr) if isinstance(value, str): value = self._subref(value) entry[keys.get(attr, attr)] = value for key, value in kpentry.custom_properties.items(): if isinstance(value, str): value = self._subref(value) entry[key] = value otpauth = self._getotpauth(kpentry.custom_properties) if otpauth: entry['otpauth'] = otpauth return entry def _getotpauth(self, properties): # KeeWeb style if 'otp' in properties: return properties['otp'] issuer = 'Imported' # KeePass 2.47 {TIMEOTP} style if 'TimeOtp-Secret-Base32' in properties: seed = properties['TimeOtp-Secret-Base32'] # TODO: support other secret formats, or actually specifying settings digits = '6' # KeeTrayTOTP style elif 'TOTP Seed' in properties: seed = properties['TOTP Seed'] # Special-case Steam if 'TOTP Settings' in properties and properties[ 'TOTP Settings'] == '30;S': # Android Password Store checks for digits==s # https://github.com/android-password-store/Android-Password-Store/blob/5e66d99c852ea67a88b650c03b0e8d55e83eccde/app/src/main/java/dev/msfjarvis/aps/util/totp/Otp.kt#L41 digits = 's' # pass-otp, via Pass::Otp, checks for issuer=~Steam # https://github.com/tadfisher/pass-otp/issues/97 # https://github.com/baierjan/Pass-OTP-perl/blob/10242388a9ce6633a3f39697e1a4b2af079b7f77/lib/Pass/OTP.pm#L140 issuer = 'Steam' else: # TODO: parse non-'30;6' settings digits = '6' else: return None # Many sites print the secret with spaces seed = seed.replace(' ', '') return f'otpauth://totp/totp-secret?secret={seed}&issuer={issuer}&digits={digits}&period=30' def _subref(self, value): while True: match = self.reference.search(value) if match is None: break cat, attid = match.group(1, 2) if cat not in ('U', 'P'): break start, end = match.start(0), match.end(0) kpentry = self.keepass.find_entries(uuid=uuid.UUID(attid))[0] if kpentry is None: value = value[:start] + value[end:] else: attr = 'password' if cat == 'P' else 'username' if hasattr(kpentry, attr): attr = getattr(kpentry, attr) value = value[:start] + \ (attr if attr is not None else '') + value[end:] else: value = value[:start] + value[end:] return value def parse(self): """Parse Keepass KDBX3 and KDBX4 files.""" for kpentry in self.keepass.entries: if self.root not in os.sep.join(kpentry.path): continue entry = self._getentry(kpentry) entry['group'] = os.path.dirname(entry['group']) if kpentry.history: for hentry in kpentry.history: history = self._getentry(hentry) history['group'] = os.path.join('History', entry['group']) self.data.append(history) for att in kpentry.attachments: attachment = dict() attachment['group'] = entry['group'] attachment['title'] = att.filename attachment['data'] = att.data self.data.append(attachment) if entry.get('attachments', None): entry['attachments'] += ", %s" % att.filename else: entry['attachments'] = att.filename self.data.append(entry) # Export methods def insert(self, entry): """Insert a password entry into KDBX encrypted vault file.""" ignore = {'password', 'path', 'title', 'group', 'data'} path = os.path.join(self.root, entry.get('path')) title = os.path.basename(path) group = os.path.dirname(path) root_group = self.keepass.root_group kpgroup = self.keepass.find_groups(path=group) if not kpgroup: for grp in group.split('/'): kpgroup = self.keepass.find_groups(path=grp) if not kpgroup: kpgroup = self.keepass.add_group(root_group, grp) root_group = kpgroup if not self.force: pkentry = self.keepass.find_entries(title=title, group=kpgroup, recursive=False, first=True) if pkentry is not None: raise PMError("An entry already exists for %s." % path) kpentry = self.keepass.add_entry(destination_group=kpgroup, title=title, username=entry.pop('login', ''), password=entry.pop('password', ''), url=entry.pop('url', None), notes=entry.pop('comments', None), tags=entry.pop('tags', None), expiry_time=entry.pop( 'expiry_time', None), icon=entry.pop('icon', None), force_creation=True) for key, value in entry.items(): if key in ignore: continue kpentry.set_custom_property(key, str(value)) if 'data' in entry: attid = self.keepass.add_binary(entry['data']) kpentry.add_attachment(attid, title) # Context manager methods def open(self): """Open the keepass repository.""" if not PYKEEPASS: raise ImportError(name='pykeepass') try: self.keepass = PyKeePass(self.prefix, password=getpassword(self.prefix), keyfile=self.keyfile) except (CredentialsError, PayloadChecksumError, HeaderChecksumError) as error: # pragma: no cover raise PMError(error) def close(self): """Close the keepass repository.""" self.keepass.save() # Format recognition methods def detecter_open(self): """Enter the tryformat context manager.""" self.file = open(self.prefix, 'rb') def detecter_close(self): """Leave the tryformat context manager.""" self.file.close() def is_format(self): """Return True if the file is a KDBX file.""" sign = self.file.read(4) if sign != self.magic: return False return True def checkheader(self, header, only=False): """No header check.""" return True @classmethod def header(cls): """No header for KDBX file.""" return ''
parser.add_argument('-i', '--input-password', type=str, help='input keepass password', metavar='password') parser.add_argument('-o', '--output-password', type=str, help='output keepass password', metavar='password') parser.add_argument('-a', '--attribute', type=str, default='extract_to', help='attribute for getting entries', metavar='attribute') args = parser.parse_args() search_attribute = args.attribute input_path = args.input_keepass output_path = args.output_keepass # ask for passwort if not passed by command line input_password = args.input_password if args.input_password else getpass(prompt='Input KeePass password:'******'Output KeePass password:'******'.*' for entry in input_keepass.find_entries(string=searchstring, regex=True): _add_entry(output_keepass, entry) # finally, save the output keepass output_keepass.save()
# load database kp = PyKeePass(dbfile, password=os.environ['PASSWORD']) group = kp.find_groups(name=groupname, first=True) count = 1 with open(tempfile, "r") as rd: # Read lines in loop for line in rd: # All lines (besides the last) will include newline, so strip it data = line.strip() data = data.split(';;;') if len(data) != 4: print("Fehlerhafte Eingabe! Zeile: " + str(count)) print(data) sys.exit() # Variablen setzen title = str(data[0]) user = str(data[1]) password = str(data[2]) notes = str(data[3]) # Eintrag in KeePassDB schreiben (temporär, nicht gespeichert) kp.add_entry(group, title, user, password, None, notes) count += 1 kp.save() sys.exit()
def init_database(args): """Create database""" # from pykeepass.pykeepass import PyKeePass # ----- setup config ----- c = ConfigParser() if os.path.exists(args.config): c.read(args.config) if args.name is None: database_name = editable_input("Database name (no spaces)", "passhole") else: database_name = args.name if database_name in c.sections(): log.error( red("There is already a database named ") + bold(database_name) + red(" in ") + bold(args.config)) sys.exit(1) else: c.add_section(database_name) if not os.path.exists(args.config): c.set(database_name, 'default', 'True') # ----- database prompt ----- if args.name is None: database_path = editable_input("Desired database path", default_database.format(database_name)) else: database_path = args.database.format(database_name) # quit if database already exists if os.path.exists(database_path): log.error(red("Found database at ") + bold(database_path)) sys.exit(1) else: c.set(database_name, 'database', database_path) # ----- password prompt ----- if args.name is None: use_password = boolean_input("Password protect database?") if use_password: password = getpass(green('Password: '******'Confirm: ')) if not password == password_confirm: log.error(red("Passwords do not match")) sys.exit(1) else: password = None c.set(database_name, 'no-password', 'True') else: password = args.password if password is None: c.set(database_name, 'no-password', 'True') # ----- keyfile prompt ----- if args.name is None: use_keyfile = boolean_input("Use a keyfile?") if use_keyfile: keyfile = editable_input("Desired keyfile path", default_keyfile.format(database_name)) else: keyfile = None else: keyfile = args.keyfile # ----- create keyfile/database/config ----- # create keyfile if keyfile is not None: keyfile = realpath(expanduser(keyfile)) log.debug("Looking for keyfile at {}".format(keyfile)) if os.path.exists(keyfile): print("Found existing keyfile at {} Exiting".format( bold(keyfile))) sys.exit(1) print("Creating keyfile at " + bold(keyfile)) os.makedirs(dirname(keyfile), exist_ok=True) c.set(database_name, 'keyfile', keyfile) with open(keyfile, 'w') as f: log.debug("keyfile contents {}".format(keyfile_contents)) f.write(keyfile_contents.format( b64encode(os.urandom(32)).decode())) database_path = realpath(expanduser(database_path)) # create database print("Creating database at {}".format(bold(database_path))) os.makedirs(dirname(database_path), exist_ok=True) shutil.copy(template_database_file, database_path) from pykeepass import PyKeePass kp = PyKeePass(database_path, password='******') kp.password = password kp.keyfile = keyfile kp.save() config = realpath(expanduser(args.config)) # create config print("Creating config at {}".format(bold(config))) os.makedirs(dirname(config), exist_ok=True) with open(config, 'w') as f: c.write(f)
class KeepassLoader: kp = NotImplemented database_path = "" password_try = "" password_check = "" password = "" group_list = [] def __init__(self, database_path, password): self.kp = PyKeePass(database_path, password) self.database_path = database_path def add_group(self, name, icon, note, parent_group): group = Group(name, icon, note, parent_group) self.kp.add_group(group.get_parent_group(), group.get_name()) self.group_list.append(group) def add_entry(self, group_path, entry_name, username, password, url, notes, icon): entry = Entry(group_path, entry_name, username, password, url, notes, icon) self.kp.add_entry(self.kp.find_groups_by_path(entry.get_group_path()), entry.get_entry_name(), entry.get_username(), entry.get_password(), url=entry.get_url(), notes=entry.get_notes(), icon=entry.get_icon()) def get_entries(self, group_path): group = self.kp.find_groups_by_path(group_path, first=True) #group = self.kp.find_groups(name="Untergruppe", first=True) print(group) print(group.entries) entry_list = [] for entry in group.entries: entry_list.append( Entry(group, entry.title, entry.username, entry.password, entry.url, entry.notes, entry.icon)) return entry_list def get_groups(self): group_list = [] groups = self.kp.groups for group in groups: if group.path != "/": group_list.append( Group(group.name, group.path, icon=group.icon, notes="", parent_group=group.parent_group)) return group_list #we wanted to turn off the automatic saving after each action and connect it instead to a button def save(self): self.kp.save() #this method sets the initial password for the newly created database def set_database_password(self, new_password): self.kp.set_credentials(new_password) self.save() #this method changes the password of existing database (therefore the old password must be typed in to prevent others changing your password) def change_database_password(self, old_password, new_password): if self.password == old_password: self.kp.set_credentials(new_password) self.save() # # Return the database filesystem path # def get_database(self): print(self.database_path) return self.database_path # # Return the root group of the database instance # def get_root_group(self): return self.kp.root_group # # Set the first password entered by the user (for comparing reasons) # def set_password_try(self, password): self.password_try = password # # Set the second password entered by the user (for comparing reasons) # def set_password_check(self, password): self.password_check = password # # Compare the first password entered by the user with the second one # def compare_passwords(self): if self.password_try == self.password_check: if self.password_try == "" and self.password_check == "": return False else: return True else: return False
def main(input_file, target_file, password): def matches(query, card): try: if query is None: return True query = query.lower() if query in card['@title'].lower(): return True # for field in card['field']: # if query in field['@name'].lower(): # return True # if '#text' in field and query in field['#text'].lower(): # return True except: pass return False if len(sys.argv) == 2: search = sys.argv[1] else: search = None db = Decrypter(input_file, password) kp = PyKeePass('New3.kdbx', password='******') kp.root_group.name = "test_title" kp.password = password entries = [] try: xml = db.decrypt() xml = parseString(xml) except: print("cannot decrypt, maybe wrong password?") sys.exit(1) for node in xml.getElementsByTagName('card'): is_template = False if node.hasAttribute('template'): is_template = (node.attributes['template'].value == 'true') if not is_template: entries.append(Entry(node)) duplicate_check = set() for entry in entries: password = "" login = "" url = "" notes = "" # extract all types the fields and their value type_value = { entry.fields[x].type: entry.fields[x].value for x in entry.fields } # map the type of the field in safeincloud to the field type in pykeepass map_types = { "password": "******", "login": "******", "website": "url", "notes": "notes" } # username and password are required fields mapped_type_value = {"username": "", "password": ""} # add all other fields with the new names mapped_type_value.update({ map_types[x]: type_value[x] for x in type_value if x in map_types }) # ensure that the title is unique title = entry.title if title in duplicate_check: i = 2 while title + str(i) in duplicate_check: i = i + 1 title = title + str(i) assert title not in duplicate_check # add entry to keepass file kp.add_entry(kp.root_group, title, **mapped_type_value) # add title to duplicate check duplicate_check.add(title) kp.save(target_file) print("file successful mapped")
def write_token_data(entry_name, kps_group, token_data, kps_pswd, history_group=None): try: kp = PyKeePass(kps_file, password=kps_pswd) entry = kp.find_entries(title=entry_name, first=True) if entry is not None: notes = entry._get_string_field('Notes') updated_notes = '' current_envs = dict() for env in notes.split('\n'): try: splitted = env.split(':') current_envs.update({splitted[0]: splitted[1]}) except: pass for updated_environment in token_data.keys(): if history_group: if updated_environment == history_group: entry.notes = updated_notes entry.expiry_time = datetime.now() + timedelta( days=365) entry.expires = True else: entry.notes = updated_notes entry.expiry_time = datetime.now() + timedelta(days=365) entry.expires = True for current_env_name, current_env_token in current_envs.items(): if current_env_name not in token_data.keys(): token_data.update({current_env_name: current_env_token}) for env_name, env_token in token_data.items(): updated_notes += env_name + ': ' + env_token + '\n' else: group = kp.find_groups(name=kps_group, first=True) entry = kp.add_entry(group, entry_name, '', '') notes = '' for env_name, env_token in token_data.items(): notes += env_name + ': ' + env_token + '\n' entry.notes = notes entry.expiry_time = datetime.now() + timedelta(days=365) entry.expires = True kp.save() return True except Exception as e: print('[ERRO] - Erro ao gravar dados no Keepass %s:\n%s' % (kps_file, str(e)), file=sys.stderr) pass