Giter VIP home page Giter VIP logo

pykeepass's Introduction

pykeepass

image

Documentation Status

image

image

This library allows you to write entries to a KeePass database.

Come chat at #pykeepass on Freenode or #pykeepass:matrix.org on Matrix.

Installation

sudo apt install python3-lxml
pip install pykeepass

Example

from pykeepass import PyKeePass

# load database
>>> kp = PyKeePass('db.kdbx', password='somePassw0rd')

# find any group by its name
>>> group = kp.find_groups(name='social', first=True)

# get the entries in a group
>>> group.entries
[Entry: "social/facebook (myusername)", Entry: "social/twitter (myusername)"]

# find any entry by its title
>>> entry = kp.find_entries(title='facebook', first=True)

# retrieve the associated password
>>> entry.password
's3cure_p455w0rd'

# update an entry
>>> entry.notes = 'primary facebook account'

# create a new group
>>> group = kp.add_group(kp.root_group, 'email')

# create a new entry
>>> kp.add_entry(group, 'gmail', 'myusername', 'myPassw0rdXX')
Entry: "email/gmail (myusername)"

# save database
>>> kp.save()

Finding Entries

find_entries (title=None, username=None, password=None, url=None, notes=None, otp=None, path=None, uuid=None, tags=None, string=None, group=None, recursive=True, regex=False, flags=None, history=False, first=False)

Returns entries which match all provided parameters, where title, username, password, url, notes, otp, autotype_window and autotype_sequence are strings, path is a list, string is a dict, autotype_enabled is a boolean, uuid is a uuid.UUID and tags is a list of strings. This function has optional regex boolean and flags string arguments, which means to interpret search strings as XSLT style regular expressions with flags.

The path list is a full path to an entry (ex. ['foobar_group', 'foobar_entry']). This implies first=True. All other arguments are ignored when this is given. This is useful for handling user input.

The string dict allows for searching custom string fields. ex. {'custom_field1': 'custom value', 'custom_field2': 'custom value'}

The group argument determines what Group to search under, and the recursive boolean controls whether to search recursively.

The history (default False) boolean controls whether history entries should be included in the search results.

The first (default False) boolean controls whether to return the first matched item, or a list of matched items.

  • if first=False, the function returns a list of Entry s or [] if there are no matches
  • if first=True, the function returns the first Entry match, or None if there are no matches

entries

a flattened list of all entries in the database

>>> kp.entries
[Entry: "foo_entry (myusername)", Entry: "foobar_entry (myusername)", Entry: "social/gmail (myusername)", Entry: "social/facebook (myusername)"]

>>> kp.find_entries(title='gmail', first=True)
Entry: "social/gmail (myusername)"

>>> kp.find_entries(title='foo.*', regex=True)
[Entry: "foo_entry (myusername)", Entry: "foobar_entry (myusername)"]

>>> entry = kp.find_entries(title='foo.*', url='.*facebook.*', regex=True, first=True)
>>> entry.url
'facebook.com'
>>> entry.title
'foo_entry'
>>> entry.title = 'hello'

>>> group = kp.find_group(name='social', first=True)
>>> kp.find_entries(title='facebook', group=group, recursive=False, first=True)
Entry: "social/facebook (myusername)"

>>> entry.otp
otpauth://totp/test:lkj?secret=TEST%3D%3D%3D%3D&period=30&digits=6&issuer=test

Finding Groups

find_groups (name=None, path=None, uuid=None, notes=None, group=None, recursive=True, regex=False, flags=None, first=False)

where name and notes are strings, path is a list, uuid is a uuid.UUID. This function has optional regex boolean and flags string arguments, which means to interpret search strings as XSLT style regular expressions with flags.

The path list is a full path to a group (ex. ['foobar_group', 'sub_group']). This implies first=True. All other arguments are ignored when this is given. This is useful for handling user input.

The group argument determines what Group to search under, and the recursive boolean controls whether to search recursively.

The first (default False) boolean controls whether to return the first matched item, or a list of matched items.

  • if first=False, the function returns a list of Group s or [] if there are no matches
  • if first=True, the function returns the first Group match, or None if there are no matches

root_group

the Root group to the database

groups

a flattened list of all groups in the database

>>> kp.groups
[Group: "foo", Group "foobar", Group: "social", Group: "social/foo_subgroup"]

>>> kp.find_groups(name='foo', first=True)
Group: "foo"

>>> kp.find_groups(name='foo.*', regex=True)
[Group: "foo", Group "foobar"]

>>> kp.find_groups(path=['social'], regex=True)
[Group: "social", Group: "social/foo_subgroup"]

>>> kp.find_groups(name='social', first=True).subgroups
[Group: "social/foo_subgroup"]

>>> kp.root_group
Group: "/"

Entry Functions and Properties

add_entry (destination_group, title, username, password, url=None, notes=None, tags=None, expiry_time=None, icon=None, force_creation=False)

delete_entry (entry)

trash_entry (entry)

move a group to the recycle bin. The recycle bin is created if it does not exit. entry must be an empty Entry.

move_entry (entry, destination_group)

atime

access time

ctime

creation time

mtime

modification time

where destination_group is a Group instance. entry is an Entry instance. title, username, password, url, notes, tags, icon are strings. expiry_time is a datetime instance.

If expiry_time is a naive datetime object (i.e. expiry_time.tzinfo is not set), the timezone is retrieved from dateutil.tz.gettz().

# add a new entry to the Root group
>>> kp.add_entry(kp.root_group, 'testing', 'foo_user', 'passw0rd')
Entry: "testing (foo_user)"

# add a new entry to the social group
>>> group = kp.find_groups(name='social', first=True)
>>> entry = kp.add_entry(group, 'testing', 'foo_user', 'passw0rd')
Entry: "testing (foo_user)"

# save the database
>>> kp.save()

# delete an entry
>>> kp.delete_entry(entry)

# move an entry
>>> kp.move_entry(entry, kp.root_group)

# save the database
>>> kp.save()

# change creation time
>>> from datetime import datetime, timezone
>>> entry.ctime = datetime(2023, 1, 1, tzinfo=timezone.utc)

# update modification or access time
>>> entry.touch(modify=True)

Group Functions and Properties

add_group (destination_group, group_name, icon=None, notes=None)

delete_group (group)

trash_group (group)

move a group to the recycle bin. The recycle bin is created if it does not exit. group must be an empty Group.

empty_group (group)

delete all entries and subgroups of a group. group is an instance of Group.

move_group (group, destination_group)

atime

access time

ctime

creation time

mtime

modification time

destination_group and group are instances of Group. group_name is a string

# add a new group to the Root group
>>> group = kp.add_group(kp.root_group, 'social')

# add a new group to the social group
>>> group2 = kp.add_group(group, 'gmail')
Group: "social/gmail"

# save the database
>>> kp.save()

# delete a group
>>> kp.delete_group(group)

# move a group
>>> kp.move_group(group2, kp.root_group)

# save the database
>>> kp.save()

# change creation time
>>> from datetime import datetime, timezone
>>> group.ctime = datetime(2023, 1, 1, tzinfo=timezone.utc)

# update modification or access time
>>> group.touch(modify=True)

Attachments

In this section, binary refers to the bytes of the attached data (stored at the root level of the database), while attachment is a reference to a binary (stored in an entry). A binary can be referenced by none, one or many attachments.

add_binary (data, compressed=True, protected=True)

where data is bytes. Adds a blob of data to the database. The attachment reference must still be added to an entry (see below). compressed only applies to KDBX3 and protected only applies to KDBX4 (no effect if used on wrong database version). Returns id of attachment.

delete_binary (id)

where id is an int. Removes binary data from the database and deletes any attachments that reference it. Since attachments reference binaries by their positional index, attachments that reference binaries with id > id will automatically be decremented.

find_attachments (id=None, filename=None, element=None, recursive=True, regex=False, flags=None, history=False, first=False)

where id is an int, filename is a string, and element is an Entry or Group to search under.

  • if first=False, the function returns a list of Attachment s or [] if there are no matches
  • if first=True, the function returns the first Attachment match, or None if there are no matches

binaries

list of bytestrings containing binary data. List index corresponds to attachment id

attachments

list containing all Attachment s in the database.

Entry.add_attachment (id, filename)

where id is an int and filename is a string. Creates a reference using the given filename to a database binary. The existence of a binary with the given id is not checked. Returns Attachment.

Entry.delete_attachment (attachment)

where attachment is an Attachment. Deletes a reference to a database binary.

Entry.attachments

list of Attachment s for this Entry.

Attachment.id

id of data that this attachment points to

Attachment.filename

string representing this attachment

Attachment.data

the data that this attachment points to. Raises BinaryError if data does not exist.

Attachment.entry

the entry that this attachment is attached to

>>> e = kp.add_entry(kp.root_group, title='foo', username='', password='')

# add attachment data to the db
>>> binary_id = kp.add_binary(b'Hello world')

>>> kp.binaries
[b'Hello world']

# add attachment reference to entry
>>> a = e.add_attachment(binary_id, 'hello.txt')
>>> a
Attachment: 'hello.txt' -> 0

# access attachments
>>> a
Attachment: 'hello.txt' -> 0
>>> a.id
0
>>> a.filename
'hello.txt'
>>> a.data
b'Hello world'
>>> e.attachments
[Attachment: 'hello.txt' -> 0]

# list all attachments in the database
>>> kp.attachments
[Attachment: 'hello.txt' -> 0]

# search attachments
>>> kp.find_attachments(filename='hello.txt')
[Attachment: 'hello.txt** -> 0]

# delete attachment reference
>>> e.delete_attachment(a)

# or, delete both attachment reference and binary
>>> kp.delete_binary(binary_id**

Credential Expiry

credchange_date

datetime object with date of last credentials change

credchange_required

boolean whether database credentials have expired and are required to change

credchange_recommended

boolean whether database credentials have expired and are recommended to change

credchange_required_days

days after credchange_date that credential update is required

credchange_recommended_days

days after credchange_date that credential update is recommended

Miscellaneous

read (filename=None, password=None, keyfile=None, transformed_key=None, decrypt=False)

where filename, password, and keyfile are strings ( filename and keyfile may also be file-like objects). filename is the path to the database, password is the master password string, and keyfile is the path to the database keyfile. At least one of password and keyfile is required. Alternatively, the derived key can be supplied directly through transformed_key. decrypt tells whether the file should be decrypted or not.

Can raise CredentialsError, HeaderChecksumError, or PayloadChecksumError.

reload ()

reload database from disk using previous credentials

save (filename=None)

where filename is the path of the file to save to (filename may also be file-like object). If filename is not given, the path given in read will be used.

password

string containing database password. Can also be set. Use None for no password.

filename

string containing path to database. Can also be set

keyfile

string containing path to the database keyfile. Can also be set. Use None for no keyfile.

version

tuple containing database version. e.g. (3, 1) is a KDBX version 3.1 database.

encryption_algorithm

string containing algorithm used to encrypt database. Possible values are aes256, chacha20, and twofish.

create_database (filename, password=None, keyfile=None, transformed_key=None)

create a new database at filename with supplied credentials. Returns PyKeePass object

tree

database lxml tree

xml

get database XML data as string

dump_xml (filename)

pretty print database XML to file

TOTP

Entry.otp

TOTP URI which can be passed to an OTP library to generate codes

# find an entry which has otp attribute
>>> e = kp.find_entries(otp='.*', regex=True, first=True)
>>> import pyotp
>>> pyotp.parse_uri(e.otp).now()
799270

Tests and Debugging

Run tests with python tests/tests.py or python tests/tests.py SomeSpecificTest

Enable debugging when doing tests in console:

>>> from pykeepass.pykeepass import debug_setup >>> debug_setup() >>> kp.entries[0] DEBUG:pykeepass.pykeepass:xpath query: //Entry DEBUG:pykeepass.pykeepass:xpath query: (ancestor::Group)[last()] DEBUG:pykeepass.pykeepass:xpath query: (ancestor::Group)[last()] DEBUG:pykeepass.pykeepass:xpath query: String/Key[text()="Title"]/../Value DEBUG:pykeepass.pykeepass:xpath query: String/Key[text()="UserName"]/../Value Entry: "root_entry (foobar_user)"

pykeepass's People

Contributors

a6gibkm avatar basak avatar boomerb avatar br-olf avatar cclauss avatar dependabot[bot] avatar disarticulate avatar ekuler avatar evidlo avatar firecat53 avatar janbrummer avatar jeffcarpenter avatar jugmac00 avatar kaciras avatar keyweeusr avatar mats-erik avatar mattllvw avatar musicinmybrain avatar pschmitt avatar pwall2222 avatar pylipp avatar pyup-bot avatar rfinnie avatar rubenv avatar sbremer avatar singuliere avatar teemusalo avatar teresafds avatar whwright avatar wpoely86 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pykeepass's Issues

Bug with get_root_group?

Seems like I can trigger the following pretty easily.

Traceback (most recent call last):
  File "t.py", line 17, in <module>
    print f.get_root_group("blah/")
  File "/Users/jxharlow/.venv/lib/python2.7/site-packages/pykeepass/pykeepass.py", line 105, in get_root_group
    return self.find_group_by_path()
TypeError: find_group_by_path() takes at least 2 arguments (1 given)

Using the following:

import pykeepass

import six

f = "blah.kdbx"

pw = 'blah'

f = pykeepass.PyKeePass(f, password=pw)


print f.get_root_group("blah/")

find entries by kwargs

Instead of having 8 different functions to find entries, it would be nice to just pass kwargs to _find_entry_by.

This way, you can actually find entries by multiple parameters, i.e:
find_entries(title=exemple, username=me...)

Would you consider a PR for this?

Case insensitive regex

This line:
lstEntry = kp.find_entries_by_notes(".Chocolate.",regex=True)
...finds the entry with notes:
Chocolate chips are a great invention

However, I cannot make the regex case-insensitive using the "i" modifier. I have tried these:
lstEntry = kp.find_entries_by_notes("/.chocolate./i",regex=True)
lstEntry = kp.find_entries_by_notes("(.chocolate.)i",regex=True)

but both fail to find "Chocolate". Is this a bug in the regex or is there another way to make the search case insensitive?
Thanks

entry.parentgroup doesn't work for history entries

Hi,

I accidentally found the following issue:

dub@gamora:~/src/tests$ python2
Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pykeepass import PyKeePass
>>> 
>>> kp = PyKeePass('tests.kdbx', 'password')
>>> 
>>> item = kp.find_entries_by_title('root_entry', first=True)
>>> print(item)
Entry: "root_entry (foobar_user)"
>>> 
>>> hist = item.history
>>> 
>>> hist_item = hist[0]
>>> print(hist_item)
Entry: "[History of: root_entry] (foobar_user)"
>>> 
>>> hist_item.parentgroup
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/pykeepass/entry.py", line 170, in parentgroup
    return pykeepass.group.Group(element=ancestor, version=self._version)
  File "/usr/local/lib/python2.7/dist-packages/pykeepass/group.py", line 39, in __init__
    'element, but a {}'.format(element.tag)
AssertionError: The provided element is not a Group element, but a Entry
>>> 

same happens with python3

dub@gamora:~/src/tests$ python3
Python 3.6.7 (default, Oct 22 2018, 11:32:17) 
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pykeepass import PyKeePass
>>> 
>>> kp = PyKeePass('tests.kdbx', 'password')
>>> 
>>> item = kp.find_entries_by_title('root_entry', first=True)
>>> print(item)
b'Entry: "root_entry (foobar_user)"'
>>> 
>>> hist = item.history
>>> 
>>> hist_item = hist[0]
>>> print(hist_item)
b'Entry: "[History of: root_entry] (foobar_user)"'
>>> 
>>> hist_item.parentgroup
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.6/dist-packages/pykeepass/entry.py", line 170, in parentgroup
    return pykeepass.group.Group(element=ancestor, version=self._version)
  File "/usr/local/lib/python3.6/dist-packages/pykeepass/group.py", line 39, in __init__
    'element, but a {}'.format(element.tag)
AssertionError: The provided element is not a Group element, but a Entry
>>> 

entry.touch() doesn't work

$ python
Python 2.7.15rc1 (default, Nov 12 2018, 14:31:15) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pykeepass import PyKeePass
>>> kp = PyKeePass('test.kdbx', 'monilove')
>>> group_name = 'Robot'
>>> entry_name = 'KPserver'
>>> group = kp.find_groups(name=group_name, first=True)
>>> item = kp.find_entries(group=group, title=entry_name, first=True)
>>> item.touch(modify=True)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/pykeepass/entry.py", line 219, in touch
    self._element.Times.LastAccessTime = datetime.utcnow()
AttributeError: 'lxml.etree._Element' object has no attribute 'Times'

Entries are defined only by their group and by their title

Hello,
Currently, entries are only defined during creation by their tree(group) and title.
Even if their username and password is different it overwrite it.
With the force_creation, t's possible to, but i think it's safer to check also at least the username before overwriting it.
Would you accept a pull request on this?
Thank you

_xpath function throws exception when title or username contains quote

When i use function

kdb.add_entry(parent_kdb_group, entry.title, entry.username, entry.password, entry.url, entry.notes)

and entry.title contains quote, in PyKeePass._xpath function xpath_str formed like this:

xpath_str = './Entry/String/Key[text()="Title"]/../Value[text()="test "inner""]/../../String/Key[text()="UserName"]/../Value[text()="some username"]/../..'

when tree.xpath() is runned it thows exception XpathEvalError: Invalid predicate
It happens because syntaxis like

Value[text()="test "inner""]

can't be used in xpath.

Can you please fix this problem?

Error while reading groups with non-ascii symbols

I have a db with groups in russian. When I'm trying to show entries I have this

>>> kp.entries
[Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 8-15: ordinal not in range(128)

Under python 3.5 works fine, but under python 2.7 - not.

Groups order sometimes reversed

I have several subgroups. And subgroups within subgroups.

When printing groups for example kp.groups. Sometimes groups are in reverse order. For example:
b'Group: "Bob/Internet"', b'Group: "Internet/Bob/Shopping"'

Is this on purpose?

`can't compare offset-naive and offset-aware datetimes` when getting expired

Here is the example code, with my values redacted:

>>> from pykeepass import PyKeePass
>>> kp = PyKeePass('REDACTED', password='REDACTED')
>>> for entry in kp.find_entries(title='entry that has an expiration'):
...     print(entry.expired)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/home/whw/.virtualenvs/kpcat/lib/python3.5/site-packages/pykeepass/baseelement.py", line 153, in expired
    return self.expires and (datetime.utcnow() > self.expiry_time)
TypeError: can't compare offset-naive and offset-aware datetimes

Upon further inspection it appears that self.expiry_time has tzinfo=UTC and datetime.utcnow() does not!

KeePassX : Unable to open the database. Invalid bool value

Steps to reproduce

  • create blank password file with keepassx
  • pip install pykeepass
  • Run with python

_

from pykeepass import PyKeePass
kp = PyKeePass('new.kdbx', password='test')
kp.add_entry(kp.root_group, 'gmail', 'myusername', 'myPassw0rdXX')
kp.save()
  • Open file in keepassx

Interestingly enough, if I just create groups, the file is still readable.
eg.

group = kp.add_group(kp.root_group,'a1')
group2 = kp.add_group(group,'a2')`

Entry already exists in group exception

Starting with version 2.8.0 I'm getting an "Entry already exists in group" exception when I have 2 entries with the same title and username in 2 different groups.

This seems related to the change from _find_exact_entry to find_entries that seems to ignore the tree variable.

attachment errors: missing data

I'm confused by a few things here, with this basic code:

with open(file_path, 'rb') as f:
	data = f.read()
	attachment_id = kp.add_binary(data)	
	entry = kp.find_entries(title=title, first=True)
	a = entry.add_attachment(attachment_id, file_name)
	print(entry, a.data)

The print gives the following error:

Traceback (most recent call last):
  ...
  File "/home/cy/anaconda3/lib/python3.6/site-packages/pykeepass/attachment.py", line 44, in data
    return self._kp.binaries[self.id]
AttributeError: 'NoneType' object has no attribute 'binaries'

add_attachment:

    def add_attachment(self, id, filename):
        element = E.Binary(
            E.Key(filename),
            E.Value(Ref=str(id))
        )
        self._element.append(element)

return pykeepass.attachment.Attachment(element=element)

Looks like it's suppose to pass in self._kp to the attachment:

@python_2_unicode_compatible
class Attachment(object):
    def __init__(self, element=None, kp=None, id=None, filename=None):
        self._element = element
        self._kp = kp

None the less, when i comment out the Print, everything is successful, but the attachment is empty:

http://imgur.com/Qv6Yo2el.png

I can produce a kdbx if you need. Its being created from kpcli.

regression in master: AttributeError: 'PyKeePass' object has no attribute 'kdbx'

The following

entries = []
with PyKeePass(filename, password=password, keyfile=keyfile) as kp:
     entries.extend(kp.entries)
entries[0].expiry_time

raises an exception as of 193b391:

File "pykeepass/pykeepass/baseelement.py", line 179, in expiry_time
 return self._get_times_property('ExpiryTime')
File "pykeepass/pykeepass/baseelement.py", line 149, in _get_times_property
  return self._decode_time(prop.text)
File "pykeepass/pykeepass/baseelement.py", line 123, in _decode_time
  if self._kp.version >= (4, 0):
File "pykeepass/pykeepass/pykeepass.py", line 83, in version
  self.kdbx.header.value.major_version,

because the kdbx data member is removed by __exit__. This regression was introduced in the attachment implementation which keeps a reference to the PyKeePass object and relies on its implementation of the version attribute which requires the kdbx data member.

save_history only working once

Hi,

I tried to add history to my entries

I'm using Python 3.6.3 on RHEL7.4
I'm using pykeepass (3.0.2)

1st run

from pykeepass import PyKeePass
kp = PyKeePass('testing.kdbx', 'secret')
group = kp.find_groups(name='python',  first=True)
item = kp.find_entries(group=group, title='hostname',username='root',first=True)
item.save_history()
kp.save()

so far so good

2nd run

from pykeepass import PyKeePass
kp = PyKeePass('testing.kdbx', 'secret')
group = kp.find_groups(name='python',  first=True)
item = kp.find_entries(group=group, title='hostname',username='root',first=True)
print(item.history)
[b'Entry: "[History of: hostname] (root)"']
item.save_history()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/rh/rh-python36/root/usr/lib/python3.6/site-packages/pykeepass/entry.py", line 229, in save_history
    archive.remove(archive.History)
AttributeError: 'lxml.etree._Element' object has no attribute 'History'

Use os.path.join

Use os.path.join instead of string manipulation in tests.py and elsewhere for crossplatform compatibility.

auto type sequence does not check for None

>>> from pykeepass import PyKeePass
>>> kp = PyKeePass('REDACTED', password='REDACTED')
>>> for entry in kp.find_entries(title='something without autotype'):
...     print(entry.autotype_sequence)
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/home/whw/Dev/pykeepass/pykeepass/entry.py", line 180, in autotype_sequence
    return self._element.find('AutoType/DefaultSequence').text
AttributeError: 'NoneType' object has no attribute 'text'

Creating new databases

Is there a way to create a new database file? I see that the database file is read as soon as a PyKeePass object is initialized.

save() is not thread safe

If a program save db in a thread while crashing in main thread, the result is an empty DB file.

save() should write to a tmp file and move result to original once done.

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd1 in position 0: invalid continuation byte

I can't seem to figure out what data I'm putting in that's not coming out.

After I save, I try to reopen the file from python:

  File "/home/cy/anaconda3/lib/python3.6/site-packages/libkeepass/__init__.py", line 42, in open
    kdb = cls(stream, **credentials)
  File "/home/cy/anaconda3/lib/python3.6/site-packages/libkeepass/kdb4.py", line 420, in __init__
    KDB4File.__init__(self, stream, **credentials)
  File "/home/cy/anaconda3/lib/python3.6/site-packages/libkeepass/kdb4.py", line 65, in __init__
    KDBFile.__init__(self, stream, **credentials)
  File "/home/cy/anaconda3/lib/python3.6/site-packages/libkeepass/common.py", line 149, in __init__
    self.read_from(stream)
  File "/home/cy/anaconda3/lib/python3.6/site-packages/libkeepass/kdb4.py", line 426, in read_from
    KDBXmlExtension.__init__(self, unprotect)
  File "/home/cy/anaconda3/lib/python3.6/site-packages/libkeepass/kdb4.py", line 313, in __init__
    self.unprotect()
  File "/home/cy/anaconda3/lib/python3.6/site-packages/libkeepass/kdb4.py", line 329, in unprotect
    unprotected_text = self._unprotect(elem.text)
  File "/home/cy/anaconda3/lib/python3.6/site-packages/libkeepass/kdb4.py", line 388, in _unprotect
    return xor(tmp, self._get_salsa(len(tmp))).decode("utf-8")
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd1 in position 0: invalid continuation byte

I can still open it in the Keepass2, remove the items, do maintenace, and save it, and it works again.

I tried the following to clean my data:

args = [arg.encode('utf-8').decode('utf-8','ignore') for arg in args]
group_entry = self.find_group(name=group_name)
user,group_name,password,host,notes = args
self.keepass_connection.add_entry(
			group_entry, 
			host,
			user,
			password,
			url=host, 
			notes=notes)
self.keepass_connection.save()

Switch to readthedocs

README.rst is starting to become unwieldy. It would be nice if we had proper Sphinx documentation on our objects.

Can't modify or create entry tags

entry.tags = ['some', 'thing'] doesn't work. Tags stay the same. I think the error is in the BaseElement._set_subelement_text tried to fix it but no luck.

Second time save creates gibberish passwords

Like described in the code itself, if we have already saved the keepass database, saving yet another time will corrupt passwords.

This is due to the fact password are encrypted anew when they are being saved. The process to encrypt password is called protect in the jargon used by libkeepass. The problem is that the lib is not unprotecting the password fields in memory after they have been saved.

Unprotects the database means the passwords will be in plain text in memory. Unprotecting first is actually needed as the libkeepass write_to() method is encrypting the passwords before saving the file content. The write_to() method does not unprotect the fields after it has completed, meaning save operations that follow will encrypt again the already encrypted password fields, leading to a corrupted database (i.e. gibberish passwords).

Unknown header field on kdbx4 database

Traceback (most recent call last):
  File "import_kdbx.py", line 32, in <module>
    main()
  File "import_kdbx.py", line 28, in main
    rows += parse_kdbx(filename, password)
  File "import_kdbx.py", line 8, in parse_kdbx
    kp = PyKeePass(filename, password=password)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/pykeepass/pykeepass.py", line 25, in __init__
    self.kdb = self.read(filename, password, keyfile)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/pykeepass/pykeepass.py", line 38, in read
    filename, **credentials
  File "/usr/lib64/python3.6/contextlib.py", line 81, in __enter__
    return next(self.gen)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/libkeepass/__init__.py", line 42, in open
    kdb = cls(stream, **credentials)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/libkeepass/kdb4.py", line 417, in __init__
    KDB4File.__init__(self, stream, **credentials)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/libkeepass/kdb4.py", line 64, in __init__
    KDBFile.__init__(self, stream, **credentials)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/libkeepass/common.py", line 149, in __init__
    self.read_from(stream, unprotect)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/libkeepass/kdb4.py", line 420, in read_from
    KDB4File.read_from(self, stream)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/libkeepass/kdb4.py", line 82, in read_from
    super(KDB4File, self).read_from(stream)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/libkeepass/common.py", line 161, in read_from
    self._read_header(stream)
  File "/home/adys/.local/share/virtualenvs/tmp-842ade25992ba7a/lib/python3.6/site-packages/libkeepass/kdb4.py", line 119, in _read_header
    raise IOError('Unknown header field found.')
OSError: Unknown header field found.

I printed the header field and field_id = 90. Any idea?

Is installing the 'tests' module intentional?

When the package is installed, it installs the tests module because there is an __init__.py in the tests/ directory and you use find_packages() in setup.py.

Just wondering if this is intentional? It seems like you shouldn't install tests with a production package, but I've seen it both ways with other packages.

Thanks!

Let programs decide the log level/config (and not pykeepass)

Seems like there is a stray 'logging.basicConfig(level=logging.DEBUG)' when you import pykeepass; this is sorta a pain in the ass since my application using pykeepass wants to setup the logging level at program execution time, and not have pykeepass try to set it up itself...

Can we please remove that line & let applications using this library do the configuration (when they want). Thanks.

No module named kdbx_parsing.kdbx

I write a littel skript:

from pykeepass import PyKeePass

load database

kp = PyKeePass('neue.kdbx', password='Password1234')

Result:

Traceback (most recent call last):
File "test.py", line 1, in
from pykeepass import PyKeePass
File "/home/sebastian/Downloads/pykeepass-master/pykeepass/pykeepass.py", line 16, in
from pykeepass.kdbx_parsing.kdbx import KDBX
ImportError: No module named kdbx_parsing.kdbx

i don't understand in pykeepass.py the row:

from pykeepass.kdbx_parsing.kdbx import KDBX

Support advanced properties

It seems like in (at least) keepass 2.x files that more than just the fixed set of properties can be used. For example I can make properties with the following names (this is from the decoded saved XML)

          <String>
            <Key>ggggg</Key>
            <Value>ggggsa</Value>
          </String>
          <String>
            <Key>sdfsdf</Key>
            <Value>s</Value>
          </String>

This is quite useful for a certain application we have that uses these custom properties to store (like ldap full CN) that shouldn't belong in notes (because it's an actual property). Seems like something simple to support; btw when trying to make a new 'custom/advanced' property using one of the restricted names (URL, Notes...) the UI basically doesn't let you (so it would make sense for the code to disallow that to).

Question about release cadence

I was wondering if there any cadence set for releases or if they are just ad-hoc. There hasn't been a release since September of last year, but quite a few fixes have gone into master branch since then. Could we get a new pypi release published with these changes, or are you holding off for a 3.1.0 at some point?

I am willing to help out with things if you need an extra set of hands.

feature request: ability to search custom fields

First of all: Thanks a lot for that lib.
My database entries are clearly idetified by a custom filed + username. I can extract that custom fields with: get_custom_property() , but i want for search it. Could you implement that?

Reuse the libkeepass context manager

Like described in the code, pykeepass has to recreate a write file stream in order to write to the context manager, because the libkeepass does only provide a context manager managing a read-only stream.

Also, since PyKeePass has been developed in a declarative way (a la C) rather than the Pythonic way, the libkeepass context manager isn't reused and pykeepass fakes the use of the libkeypass context manager by calling its underlying method (open.__enter__()) manually.

And finally, when calling the resource manager manually, depending on the garbage collector implementation (e.g.: CPython), the file descriptor might not be freed correctly leading in resources/memory leaks. The garbage collector never calls __exit__() explicitly, only with-statements do that. It's generally bad to depend on the garbage collector to trigger cleanup code anyways. This is why closing the file stream from the underlying library is recommended (especially and the mechanisms included in that lib are being deconstructed by the piece of software making use of it).

Suggestion to clarify readme

If I may suggest, the documentation could be significantly clarified with the following modification.

The function 'add_entry' seems to expect that 'group' is the object created earlier from the pk.find_entry_by_title() example.

# write a new entry pk.add_entry( group, 'new_entry', 'myusername', 'myPassw0rdXX' )

Referencing an object for 'group' yields the following error:
AttributeError: 'Group' object has no attribute 'lstrip'

Providing a string instead of object works perfectly:
# write a new entry pk.add_entry( 'group_name', 'new_entry', 'myusername', 'myPassw0rdXX' )

Alternatively you could probably add in a check for isinstance(group, pykeepass.group.Group) to check if the variable is the object, then refer to group.name. Of course, this is a bit more work than just changing a few characters on the readme (^_^)

How does search with uuid work?

I have tried doing

DB.find_entry_by_uuid(DB.find_entries_by_username("username")[1].uuid)

but I get the folllowing error

ValueError: badly formed hexadecimal UUID string

Sorry if this is a noob question, but how can I get the uuid?

Thanks for the library @pschmitt , This is one of the most well documented and complete libs about keepass!

libkeepass maintainer

I'm not sure where else to ask, but who is the current maintainer of libkeepass? pypi says that the project homepage is phpwutz/libkeepass, but this project has many forks of which yours is the most recently updated.

String field Enable in-memory protection.

Hello,
Sorry of this exists, but I need to set the Enable in-memory protection option in the string fields.
I am creating and export script to convert some entries I have to a keepass database, but the application used to read the resulted database, needs some custonm string fields. I can add those no proplem with that, but I also need to set the Enable in-memory protection option for it to work.

Is it possible to implement that or give me some direction on how to implement that myself?

Thank you.

Python 3 compatibility

I installed this using pip3 and ran around in circles trying to solve the following error :

>>> from pykeepass import PyKeePass
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
ImportError: No module named 'pykeepass'`

Took me some time to figure out that this project is only running correctly under Python 2.

This should be made clear on the documentation.

Feature request: Support field references

In [9]: kp.find_entries(title='Test Entry', first=True).password
Out[9]: '{REF:P@I:49EF54B695EB3E4591F3F07D26D4124C}'

Ideally, this would automatically dereference that and return the password, or there should be methods to track it down, like maybe:

kp.deref(entry.password)

Ultimately, there should also be a method to link one entry's field to another entry's field, perhaps like:

existing_entry = kp.find_entries(title='Production Entry', first=True)
url = 'https://test-site.example.com/?user=%s' % existing_entry.ref('username')
new_entry = kp.add_entry('Testing',
                         title='Test Entry',
                         username=existing_entry.ref('username'),
                         password=existing_entry.ref('password'),
                         url=url)

Protected String fields loose internal whitespace/newline

Hi,

We have entries with additional string fields that are multi-lined (i.e. contain new lines). We have found that if you mark the string field as memory protected it will loose its white space, when read via pykeepass (but not in keepass2). In particular the multi-line strings will become a single line string. "Unprotected" string fields keep their formatting, via pykeepass.

BR,
Thorben

Accessing attachments

I tried to use this library for processing an existing Keepassx database that contains attachments.

Here's my test database and the code I used to inspect it:

example_database.kdbx.gz

from pykeepass import PyKeePass

# load database
kp = PyKeePass('example_database.kdbx', password='fubar')

# find any group by its name
group = kp.find_groups(name='Example2', first=True)

# get the entries in a group
print(group.entries)

# find any entry by its title
entry = kp.find_entries(title='foo', first=True)

# dump it
print(entry.dump_xml().decode())

It turns out the entry XML dump contains a reference to the attachment but there's no obvious way to get the binary, since it's missing from the XML data:

<Binary>
	<Key>hello.txt</Key>
	<Value Ref="0"/>
</Binary>

Is this expected or I am just missing anything?

attachment.data returns the oldest content, not the latest

Hi,

We've found the following code:

...
for entry in entries:
  ...
     attachment = entry.attachments[0]
     secret = attachment.data

only works when you delete the history.

Doesn't matter if you delete the attachment or add it with a different name.

I've attached a test file (password: password) where I've added a PNG file, deleted it and added a text file. If you run the code it will still display the binary data.

kbdx file: https://drive.google.com/open?id=1ms3Jobn_rxfDTrmK2bE2x6Mn3OZzxIMK

Also, I'm writing this file with the latest version of KeePassX on Mac (2.0.3).

Behavior of 'path'

What should path do?

Initially we intended for it to be a way to directly select a single entry or group, but later on its behavior was modified to also mean the path to search under (this was befor group was added).

It doesn't make sense to do both and it makes _find() a pain, so we should pick one behavior and stick with it..

I'm of the opinion that when path is present, all other search terms should be ignored and the entry/group returned directly (like the original behavior of find_x_by_path). People can use group to do searches under a specific element.

>>> kp.find_entries(path='foobar_group/foobar_entry', title='blah', password='blah')
Entry: "foobar_group/foobar_entry (foobar_user)"

And I suppose path implies first=True.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.