From dc626ad29ea75c5be34146214eb1b28ca510e7bf Mon Sep 17 00:00:00 2001 From: Date: Tue, 3 Dec 2024 12:01:26 -0600 Subject: [PATCH] Update documentation --- .pages | 4 - README.md | 94 - attachment.md | 95 - baseelement.md | 163 -- entry.md | 729 ----- exceptions.md | 81 - group.md | 189 -- icons.md | 87 - index.html | 7 + kdbx_parsing.common.md | 495 ---- kdbx_parsing.kdbx.md | 16 - kdbx_parsing.kdbx3.md | 31 - kdbx_parsing.kdbx4.md | 57 - kdbx_parsing.md | 86 - kdbx_parsing.pytwofish.md | 343 --- kdbx_parsing.twofish.md | 397 --- pykeepass.html | 5474 +++++++++++++++++++++++++++++++++++++ pykeepass.md | 809 ------ setters.md | 75 - version.md | 16 - xpath.md | 22 - 21 files changed, 5481 insertions(+), 3789 deletions(-) delete mode 100644 .pages delete mode 100644 README.md delete mode 100644 attachment.md delete mode 100644 baseelement.md delete mode 100644 entry.md delete mode 100644 exceptions.md delete mode 100644 group.md delete mode 100644 icons.md create mode 100644 index.html delete mode 100644 kdbx_parsing.common.md delete mode 100644 kdbx_parsing.kdbx.md delete mode 100644 kdbx_parsing.kdbx3.md delete mode 100644 kdbx_parsing.kdbx4.md delete mode 100644 kdbx_parsing.md delete mode 100644 kdbx_parsing.pytwofish.md delete mode 100644 kdbx_parsing.twofish.md create mode 100644 pykeepass.html delete mode 100644 pykeepass.md delete mode 100644 setters.md delete mode 100644 version.md delete mode 100644 xpath.md diff --git a/.pages b/.pages deleted file mode 100644 index db48efa2..00000000 --- a/.pages +++ /dev/null @@ -1,4 +0,0 @@ -title: API Reference -nav: - - Overview: README.md - - ... diff --git a/README.md b/README.md deleted file mode 100644 index f0ac88e5..00000000 --- a/README.md +++ /dev/null @@ -1,94 +0,0 @@ - - -# API Overview - -## Modules - -- [`attachment`](./attachment.md#module-attachment) -- [`baseelement`](./baseelement.md#module-baseelement) -- [`entry`](./entry.md#module-entry) -- [`exceptions`](./exceptions.md#module-exceptions) -- [`group`](./group.md#module-group) -- [`icons`](./icons.md#module-icons) -- [`kdbx_parsing`](./kdbx_parsing.md#module-kdbx_parsing) -- [`kdbx_parsing.common`](./kdbx_parsing.common.md#module-kdbx_parsingcommon) -- [`kdbx_parsing.kdbx`](./kdbx_parsing.kdbx.md#module-kdbx_parsingkdbx) -- [`kdbx_parsing.kdbx3`](./kdbx_parsing.kdbx3.md#module-kdbx_parsingkdbx3) -- [`kdbx_parsing.kdbx4`](./kdbx_parsing.kdbx4.md#module-kdbx_parsingkdbx4) -- [`kdbx_parsing.pytwofish`](./kdbx_parsing.pytwofish.md#module-kdbx_parsingpytwofish) -- [`kdbx_parsing.twofish`](./kdbx_parsing.twofish.md#module-kdbx_parsingtwofish) -- [`pykeepass`](./pykeepass.md#module-pykeepass) -- [`setters`](./setters.md#module-setters) -- [`version`](./version.md#module-version) -- [`xpath`](./xpath.md#module-xpath) - -## Classes - -- [`attachment.Attachment`](./attachment.md#class-attachment) -- [`baseelement.BaseElement`](./baseelement.md#class-baseelement): Entry and Group inherit from this class -- [`entry.Entry`](./entry.md#class-entry) -- [`entry.HistoryEntry`](./entry.md#class-historyentry) -- [`exceptions.BinaryError`](./exceptions.md#class-binaryerror) -- [`exceptions.CredentialsError`](./exceptions.md#class-credentialserror) -- [`exceptions.HeaderChecksumError`](./exceptions.md#class-headerchecksumerror) -- [`exceptions.PayloadChecksumError`](./exceptions.md#class-payloadchecksumerror) -- [`exceptions.UnableToSendToRecycleBin`](./exceptions.md#class-unabletosendtorecyclebin) -- [`group.Group`](./group.md#class-group) -- [`common.AES256Payload`](./kdbx_parsing.common.md#class-aes256payload) -- [`common.ARCFourVariantStream`](./kdbx_parsing.common.md#class-arcfourvariantstream) -- [`common.ChaCha20Payload`](./kdbx_parsing.common.md#class-chacha20payload) -- [`common.ChaCha20Stream`](./kdbx_parsing.common.md#class-chacha20stream) -- [`common.Concatenated`](./kdbx_parsing.common.md#class-concatenated): Data Blocks <---> Bytes -- [`common.CredentialsError`](./kdbx_parsing.common.md#class-credentialserror) -- [`common.Decompressed`](./kdbx_parsing.common.md#class-decompressed): Compressed Bytes <---> Decompressed Bytes -- [`common.DecryptedPayload`](./kdbx_parsing.common.md#class-decryptedpayload): Encrypted Bytes <---> Decrypted Bytes -- [`common.DynamicDict`](./kdbx_parsing.common.md#class-dynamicdict): ListContainer <---> Container -- [`common.HeaderChecksumError`](./kdbx_parsing.common.md#class-headerchecksumerror) -- [`common.PayloadChecksumError`](./kdbx_parsing.common.md#class-payloadchecksumerror) -- [`common.Salsa20Stream`](./kdbx_parsing.common.md#class-salsa20stream) -- [`common.TwoFishPayload`](./kdbx_parsing.common.md#class-twofishpayload) -- [`common.UnprotectedStream`](./kdbx_parsing.common.md#class-unprotectedstream): lxml etree <---> unprotected lxml etree -- [`common.XML`](./kdbx_parsing.common.md#class-xml): Bytes <---> lxml etree -- [`pytwofish.TWI`](./kdbx_parsing.pytwofish.md#class-twi) -- [`pytwofish.Twofish`](./kdbx_parsing.pytwofish.md#class-twofish) -- [`twofish.BlockCipher`](./kdbx_parsing.twofish.md#class-blockcipher): Base class for all blockciphers -- [`twofish.CBC`](./kdbx_parsing.twofish.md#class-cbc): CBC chaining mode -- [`twofish.python_Twofish`](./kdbx_parsing.twofish.md#class-python_twofish) -- [`twofish.python_Twofish`](./kdbx_parsing.twofish.md#class-python_twofish) -- [`pykeepass.PyKeePass`](./pykeepass.md#class-pykeepass): Open a KeePass database - -## Functions - -- [`common.Reparsed`](./kdbx_parsing.common.md#function-reparsed) -- [`common.Unprotect`](./kdbx_parsing.common.md#function-unprotect): Select stream cipher based on protected_stream_id -- [`common.aes_kdf`](./kdbx_parsing.common.md#function-aes_kdf): Set up a context for AES128-ECB encryption to find transformed_key -- [`common.compute_key_composite`](./kdbx_parsing.common.md#function-compute_key_composite): Compute composite key. -- [`common.compute_master`](./kdbx_parsing.common.md#function-compute_master): Computes master key from transformed key and master seed. -- [`kdbx3.compute_transformed`](./kdbx_parsing.kdbx3.md#function-compute_transformed): Compute transformed key for opening database -- [`kdbx4.compute_header_hmac_hash`](./kdbx_parsing.kdbx4.md#function-compute_header_hmac_hash): Compute HMAC-SHA256 hash of header. -- [`kdbx4.compute_payload_block_hash`](./kdbx_parsing.kdbx4.md#function-compute_payload_block_hash): Compute hash of each payload block. -- [`kdbx4.compute_transformed`](./kdbx_parsing.kdbx4.md#function-compute_transformed): Compute transformed key for opening database -- [`pytwofish.byte`](./kdbx_parsing.pytwofish.md#function-byte) -- [`pytwofish.byteswap32`](./kdbx_parsing.pytwofish.md#function-byteswap32) -- [`pytwofish.decrypt`](./kdbx_parsing.pytwofish.md#function-decrypt) -- [`pytwofish.encrypt`](./kdbx_parsing.pytwofish.md#function-encrypt) -- [`pytwofish.gen_mk_tab`](./kdbx_parsing.pytwofish.md#function-gen_mk_tab) -- [`pytwofish.gen_mtab`](./kdbx_parsing.pytwofish.md#function-gen_mtab) -- [`pytwofish.gen_qtab`](./kdbx_parsing.pytwofish.md#function-gen_qtab) -- [`pytwofish.h_fun`](./kdbx_parsing.pytwofish.md#function-h_fun) -- [`pytwofish.mds_rem`](./kdbx_parsing.pytwofish.md#function-mds_rem) -- [`pytwofish.qp`](./kdbx_parsing.pytwofish.md#function-qp) -- [`pytwofish.rotl32`](./kdbx_parsing.pytwofish.md#function-rotl32) -- [`pytwofish.rotr32`](./kdbx_parsing.pytwofish.md#function-rotr32) -- [`pytwofish.set_key`](./kdbx_parsing.pytwofish.md#function-set_key) -- [`pykeepass.create_database`](./pykeepass.md#function-create_database) -- [`pykeepass.debug_setup`](./pykeepass.md#function-debug_setup): Convenience function to quickly enable debug messages -- [`setters.get_text`](./setters.md#function-get_text) -- [`setters.get_time`](./setters.md#function-get_time) -- [`setters.set_text`](./setters.md#function-set_text) -- [`setters.set_time`](./setters.md#function-set_time) - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/attachment.md b/attachment.md deleted file mode 100644 index 2ec27985..00000000 --- a/attachment.md +++ /dev/null @@ -1,95 +0,0 @@ - - - - -# module `attachment` - - - - - - ---- - - - -## class `Attachment` - - - - - - -### method `__init__` - -```python -__init__(element=None, kp=None, id=None, filename=None) -``` - - - - - - ---- - -#### property binary - - - - - ---- - -#### property data - - - - - ---- - -#### property entry - - - - - ---- - -#### property filename - - - - - ---- - -#### property id - - - - - - - ---- - - - -### method `delete` - -```python -delete() -``` - - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/baseelement.md b/baseelement.md deleted file mode 100644 index 8cb035a9..00000000 --- a/baseelement.md +++ /dev/null @@ -1,163 +0,0 @@ - - - - -# module `baseelement` - - - - - - ---- - - - -## class `BaseElement` -Entry and Group inherit from this class - - - -### method `__init__` - -```python -__init__(element, kp=None, icon=None, expires=False, expiry_time=None) -``` - - - - - - ---- - -#### property atime - - - - - ---- - -#### property ctime - - - - - ---- - -#### property expired - - - - - ---- - -#### property expires - - - - - ---- - -#### property expiry_time - - - - - ---- - -#### property group - - - - - ---- - -#### property icon - - - - - ---- - -#### property mtime - - - - - ---- - -#### property parentgroup - - - - - ---- - -#### property uuid - -Returns uuid of this element as a uuid.UUID object - - - ---- - - - -### method `delete` - -```python -delete() -``` - - - - - ---- - - - -### method `dump_xml` - -```python -dump_xml(pretty_print=False) -``` - - - - - ---- - - - -### method `touch` - -```python -touch(modify=False) -``` - -Update last access time of an entry/group - - - -**Args:** - - - `modify` (bool): update access time as well a modification time - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/entry.md b/entry.md deleted file mode 100644 index 3c3dd83f..00000000 --- a/entry.md +++ /dev/null @@ -1,729 +0,0 @@ - - - - -# module `entry` - - - - -**Global Variables** ---------------- -- **reserved_keys** - - ---- - - - -## class `Entry` - - - - - - -### method `__init__` - -```python -__init__( - title=None, - username=None, - password=None, - url=None, - notes=None, - otp=None, - tags=None, - expires=False, - expiry_time=None, - icon=None, - autotype_sequence=None, - autotype_enabled=True, - element=None, - kp=None -) -``` - - - - - - ---- - -#### property atime - - - - - ---- - -#### property attachments - - - - - ---- - -#### property autotype_enabled - - - - - ---- - -#### property autotype_sequence - - - - - ---- - -#### property ctime - - - - - ---- - -#### property custom_properties - - - - - ---- - -#### property expired - - - - - ---- - -#### property expires - - - - - ---- - -#### property expiry_time - - - - - ---- - -#### property group - - - - - ---- - -#### property history - - - - - ---- - -#### property icon - - - - - ---- - -#### property is_a_history_entry - - - - - ---- - -#### property mtime - - - - - ---- - -#### property notes - - - - - ---- - -#### property otp - - - - - ---- - -#### property parentgroup - - - - - ---- - -#### property password - - - - - ---- - -#### property path - -Path to element as list. List contains all parent group names ending with entry title. List may contain strings or NoneTypes. - ---- - -#### property tags - - - - - ---- - -#### property title - - - - - ---- - -#### property url - - - - - ---- - -#### property username - - - - - ---- - -#### property uuid - -Returns uuid of this element as a uuid.UUID object - - - ---- - - - -### method `add_attachment` - -```python -add_attachment(id, filename) -``` - - - - - ---- - - - -### method `delete_attachment` - -```python -delete_attachment(attachment) -``` - - - - - ---- - - - -### method `delete_custom_property` - -```python -delete_custom_property(key) -``` - - - - - ---- - - - -### method `delete_history` - -```python -delete_history(history_entry=None, all=False) -``` - -Delete entries from history - - - -**Args:** - - - `history_entry` (Entry): history item to delete - - `all` (bool): delete all entries from history. Default is False - ---- - - - -### method `deref` - -```python -deref(attribute) -``` - - - - - ---- - - - -### method `get_custom_property` - -```python -get_custom_property(key) -``` - - - - - ---- - - - -### method `ref` - -```python -ref(attribute) -``` - -Create reference to an attribute of this element. - ---- - - - -### method `save_history` - -```python -save_history() -``` - -Save the entry in its history - ---- - - - -### method `set_custom_property` - -```python -set_custom_property(key, value) -``` - - - - - - ---- - - - -## class `HistoryEntry` - - - - - - -### method `__init__` - -```python -__init__( - title=None, - username=None, - password=None, - url=None, - notes=None, - otp=None, - tags=None, - expires=False, - expiry_time=None, - icon=None, - autotype_sequence=None, - autotype_enabled=True, - element=None, - kp=None -) -``` - - - - - - ---- - -#### property atime - - - - - ---- - -#### property attachments - - - - - ---- - -#### property autotype_enabled - - - - - ---- - -#### property autotype_sequence - - - - - ---- - -#### property ctime - - - - - ---- - -#### property custom_properties - - - - - ---- - -#### property expired - - - - - ---- - -#### property expires - - - - - ---- - -#### property expiry_time - - - - - ---- - -#### property group - - - - - ---- - -#### property history - - - - - ---- - -#### property icon - - - - - ---- - -#### property is_a_history_entry - - - - - ---- - -#### property mtime - - - - - ---- - -#### property notes - - - - - ---- - -#### property otp - - - - - ---- - -#### property parentgroup - - - - - ---- - -#### property password - - - - - ---- - -#### property path - -Path to element as list. List contains all parent group names ending with entry title. List may contain strings or NoneTypes. - ---- - -#### property tags - - - - - ---- - -#### property title - - - - - ---- - -#### property url - - - - - ---- - -#### property username - - - - - ---- - -#### property uuid - -Returns uuid of this element as a uuid.UUID object - - - ---- - - - -### method `add_attachment` - -```python -add_attachment(id, filename) -``` - - - - - ---- - - - -### method `delete_attachment` - -```python -delete_attachment(attachment) -``` - - - - - ---- - - - -### method `delete_custom_property` - -```python -delete_custom_property(key) -``` - - - - - ---- - - - -### method `delete_history` - -```python -delete_history(history_entry=None, all=False) -``` - -Delete entries from history - - - -**Args:** - - - `history_entry` (Entry): history item to delete - - `all` (bool): delete all entries from history. Default is False - ---- - - - -### method `deref` - -```python -deref(attribute) -``` - - - - - ---- - - - -### method `get_custom_property` - -```python -get_custom_property(key) -``` - - - - - ---- - - - -### method `ref` - -```python -ref(attribute) -``` - -Create reference to an attribute of this element. - ---- - - - -### method `save_history` - -```python -save_history() -``` - -Save the entry in its history - ---- - - - -### method `set_custom_property` - -```python -set_custom_property(key, value) -``` - - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/exceptions.md b/exceptions.md deleted file mode 100644 index 7ef9e8c5..00000000 --- a/exceptions.md +++ /dev/null @@ -1,81 +0,0 @@ - - - - -# module `exceptions` - - - - - - ---- - - - -## class `CredentialsError` - - - - - - - - ---- - - - -## class `PayloadChecksumError` - - - - - - - - ---- - - - -## class `HeaderChecksumError` - - - - - - - - ---- - - - -## class `BinaryError` - - - - - - - - ---- - - - -## class `UnableToSendToRecycleBin` - - - - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/group.md b/group.md deleted file mode 100644 index c64640e0..00000000 --- a/group.md +++ /dev/null @@ -1,189 +0,0 @@ - - - - -# module `group` - - - - - - ---- - - - -## class `Group` - - - - - - -### method `__init__` - -```python -__init__( - name=None, - element=None, - icon=None, - notes=None, - kp=None, - expires=None, - expiry_time=None -) -``` - - - - - - ---- - -#### property atime - - - - - ---- - -#### property ctime - - - - - ---- - -#### property entries - - - - - ---- - -#### property expired - - - - - ---- - -#### property expires - - - - - ---- - -#### property expiry_time - - - - - ---- - -#### property group - - - - - ---- - -#### property icon - - - - - ---- - -#### property is_root_group - - - - - ---- - -#### property mtime - - - - - ---- - -#### property name - - - - - ---- - -#### property notes - - - - - ---- - -#### property parentgroup - - - - - ---- - -#### property path - - - - - ---- - -#### property subgroups - - - - - ---- - -#### property uuid - -Returns uuid of this element as a uuid.UUID object - - - ---- - - - -### method `append` - -```python -append(entries) -``` - - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/icons.md b/icons.md deleted file mode 100644 index 75c0d282..00000000 --- a/icons.md +++ /dev/null @@ -1,87 +0,0 @@ - - - - -# module `icons` - - - - -**Global Variables** ---------------- -- **KEY** -- **GLOBE** -- **WARNING_SIGN** -- **SERVER** -- **PINNED_NOTE** -- **SPEECH_BUBBLE** -- **SQUARES** -- **HANDWRITTEN_NOTE** -- **GLOBE_PLUG** -- **BUSINESS_CARD** -- **GREEN_AND_WHITE_THINGY** -- **CAMERA** -- **INFRARED** -- **KEYS** -- **POWER_PLUG** -- **FLATBED_SCANNER** -- **GLOBE_STAR** -- **CD_ROM** -- **MONITOR** -- **ENVELOPE** -- **GEAR** -- **CHECKLIST** -- **NOTEPAD** -- **DESKTOP** -- **POWER_FLASH** -- **FOLDER_ENVELOPE** -- **FLOPPY_DISK** -- **SERVER_2** -- **GREEN_DOT** -- **MONITOR_KEY** -- **SHELL** -- **PRINTER** -- **DASHBOARD** -- **CROATIA** -- **WRENCH** -- **PC_INTERNET** -- **ZIP_FILE** -- **PERCENT_SIGN** -- **SAMBA_SHARE** -- **CLOCK** -- **SEARCH** -- **USED_TAMPON** -- **MEMORY_STICK** -- **RECYCLE_BIN** -- **POST_IT** -- **RED_CROSS** -- **INFO_SIGN** -- **CARDBOARD** -- **FOLDER** -- **FOLDER_OPEN** -- **FOLDER_CUBE** -- **LOCK_OPEN** -- **LOCK_CLOSED** -- **GREEN_CHECKMARK** -- **FEATHER_PEN** -- **POLAROID_PICTURE** -- **BOOK_OPENED** -- **UI** -- **MANAGER** -- **HAMMER** -- **HOUSE** -- **STAR** -- **PENGUIN** -- **FEATHER** -- **APPLE** -- **WIKIPEDIA** -- **DOLLAR_SIGN** -- **CERTIFICATE** -- **SMARTPHONE** - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/index.html b/index.html new file mode 100644 index 00000000..5d1b63ad --- /dev/null +++ b/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/kdbx_parsing.common.md b/kdbx_parsing.common.md deleted file mode 100644 index 148b19ec..00000000 --- a/kdbx_parsing.common.md +++ /dev/null @@ -1,495 +0,0 @@ - - - - -# module `kdbx_parsing.common` - - - - - ---- - - - -## function `Reparsed` - -```python -Reparsed(subcon_out) -``` - - - - - - ---- - - - -## function `aes_kdf` - -```python -aes_kdf(key, rounds, key_composite) -``` - -Set up a context for AES128-ECB encryption to find transformed_key - - ---- - - - -## function `compute_key_composite` - -```python -compute_key_composite(password=None, keyfile=None) -``` - -Compute composite key. Used in header verification and payload decryption. - - ---- - - - -## function `compute_master` - -```python -compute_master(context) -``` - -Computes master key from transformed key and master seed. Used in payload decryption. - - ---- - - - -## function `Unprotect` - -```python -Unprotect(protected_stream_id, protected_stream_key, subcon) -``` - -Select stream cipher based on protected_stream_id - - ---- - - - -## class `HeaderChecksumError` - - - - - - - - ---- - - - -## class `CredentialsError` - - - - - - - - ---- - - - -## class `PayloadChecksumError` - - - - - - - - ---- - - - -## class `DynamicDict` -ListContainer <---> Container Convenience mapping so we dont have to iterate ListContainer to find the right item - -FIXME: lump kwarg was added to get around the fact that InnerHeader is not truly a dict. We lump all 'binary' InnerHeaderItems into a single list - - - -### method `__init__` - -```python -__init__(key, subcon, lump=[]) -``` - - - - - - - - - ---- - - - -## class `XML` -Bytes <---> lxml etree - - - - - ---- - - - -## class `UnprotectedStream` -lxml etree <---> unprotected lxml etree Iterate etree for Protected elements and decrypt using cipher provided by get_cipher - - - -### method `__init__` - -```python -__init__(protected_stream_key, subcon) -``` - - - - - - - - - ---- - - - -## class `ARCFourVariantStream` - - - - - - -### method `__init__` - -```python -__init__(protected_stream_key, subcon) -``` - - - - - - - - ---- - - - -### method `get_cipher` - -```python -get_cipher(protected_stream_key) -``` - - - - - - ---- - - - -## class `Salsa20Stream` - - - - - - -### method `__init__` - -```python -__init__(protected_stream_key, subcon) -``` - - - - - - - - ---- - - - -### method `get_cipher` - -```python -get_cipher(protected_stream_key) -``` - - - - - - ---- - - - -## class `ChaCha20Stream` - - - - - - -### method `__init__` - -```python -__init__(protected_stream_key, subcon) -``` - - - - - - - - ---- - - - -### method `get_cipher` - -```python -get_cipher(protected_stream_key) -``` - - - - - - ---- - - - -## class `Concatenated` -Data Blocks <---> Bytes - - - - - ---- - - - -## class `DecryptedPayload` -Encrypted Bytes <---> Decrypted Bytes - - - - - ---- - - - -## class `AES256Payload` - - - - - - - ---- - - - -### method `get_cipher` - -```python -get_cipher(master_key, encryption_iv) -``` - - - - - ---- - - - -### method `pad` - -```python -pad(data) -``` - - - - - ---- - - - -### method `unpad` - -```python -unpad(data) -``` - - - - - - ---- - - - -## class `ChaCha20Payload` - - - - - - - ---- - - - -### method `get_cipher` - -```python -get_cipher(master_key, encryption_iv) -``` - - - - - ---- - - - -### method `pad` - -```python -pad(data) -``` - - - - - ---- - - - -### method `unpad` - -```python -unpad(data) -``` - - - - - - ---- - - - -## class `TwoFishPayload` - - - - - - - ---- - - - -### method `get_cipher` - -```python -get_cipher(master_key, encryption_iv) -``` - - - - - ---- - - - -### method `pad` - -```python -pad(data) -``` - - - - - ---- - - - -### method `unpad` - -```python -unpad(data) -``` - - - - - - ---- - - - -## class `Decompressed` -Compressed Bytes <---> Decompressed Bytes - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/kdbx_parsing.kdbx.md b/kdbx_parsing.kdbx.md deleted file mode 100644 index a011cb46..00000000 --- a/kdbx_parsing.kdbx.md +++ /dev/null @@ -1,16 +0,0 @@ - - - - -# module `kdbx_parsing.kdbx` - - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/kdbx_parsing.kdbx3.md b/kdbx_parsing.kdbx3.md deleted file mode 100644 index b0b1a540..00000000 --- a/kdbx_parsing.kdbx3.md +++ /dev/null @@ -1,31 +0,0 @@ - - - - -# module `kdbx_parsing.kdbx3` - - - - -**Global Variables** ---------------- -- **kdf_uuids** - ---- - - - -## function `compute_transformed` - -```python -compute_transformed(context) -``` - -Compute transformed key for opening database - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/kdbx_parsing.kdbx4.md b/kdbx_parsing.kdbx4.md deleted file mode 100644 index 717f0bc6..00000000 --- a/kdbx_parsing.kdbx4.md +++ /dev/null @@ -1,57 +0,0 @@ - - - - -# module `kdbx_parsing.kdbx4` - - - - -**Global Variables** ---------------- -- **kdf_uuids** - ---- - - - -## function `compute_transformed` - -```python -compute_transformed(context) -``` - -Compute transformed key for opening database - - ---- - - - -## function `compute_header_hmac_hash` - -```python -compute_header_hmac_hash(context) -``` - -Compute HMAC-SHA256 hash of header. Used to prevent header tampering. - - ---- - - - -## function `compute_payload_block_hash` - -```python -compute_payload_block_hash(this) -``` - -Compute hash of each payload block. Used to prevent payload corruption and tampering. - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/kdbx_parsing.md b/kdbx_parsing.md deleted file mode 100644 index bb9545d1..00000000 --- a/kdbx_parsing.md +++ /dev/null @@ -1,86 +0,0 @@ - - - - -# module `kdbx_parsing` - - - - -**Global Variables** ---------------- -- **pytwofish**: ## twofish.py - pure Python implementation of the Twofish algorithm. -## Bjorn Edstrom 13 december 2007. -## -## Copyrights -## ========== -## -## This code is a derived from an implementation by Dr Brian Gladman -## (gladman@seven77.demon.co.uk) which is subject to the following license. -## This Python implementation is not subject to any other license. -## -##/* This is an independent implementation of the encryption algorithm: */ -##/* */ -##/* Twofish by Bruce Schneier and colleagues */ -##/* */ -##/* which is a candidate algorithm in the Advanced Encryption Standard */ -##/* programme of the US National Institute of Standards and Technology. */ -##/* */ -##/* Copyright in this implementation is held by Dr B R Gladman but I */ -##/* hereby give permission for its free direct or derivative use subject */ -##/* to acknowledgment of its origin and compliance with any conditions */ -##/* that the originators of t he algorithm place on its exploitation. */ -##/* */ -##/* My thanks to Doug Whiting and Niels Ferguson for comments that led */ -##/* to improvements in this implementation. */ -##/* */ -##/* Dr Brian Gladman (gladman@seven77.demon.co.uk) 14th January 1999 */ -## -## The above copyright notice must not be removed. -## -## Information -## =========== -## -## Anyone thinking of using this code should reconsider. It's slow. -## Try python-mcrypt instead. In case a faster library is not installed -## on the target system, this code can be used as a portable fallback. - -- **twofish**: # ============================================================================= -# Copyright (c) 2008 Christophe Oosterlynck -# & NXP ( Philippe Teuwen ) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# ============================================================================= -# -*- coding: utf-8 -*- - -- **common** -- **kdbx3**: # Evan Widloski - 2018-04-11 -# keepass decrypt experimentation - -- **kdbx4**: # Evan Widloski - 2018-04-11 -# keepass decrypt experimentation - -- **kdbx** - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/kdbx_parsing.pytwofish.md b/kdbx_parsing.pytwofish.md deleted file mode 100644 index cf3cee72..00000000 --- a/kdbx_parsing.pytwofish.md +++ /dev/null @@ -1,343 +0,0 @@ - - - - -# module `kdbx_parsing.pytwofish` - - - - -**Global Variables** ---------------- -- **block_size** -- **key_size** -- **WORD_BIGENDIAN** -- **tab_5b** -- **tab_ef** -- **ror4** -- **ashx** -- **qt0** -- **qt1** -- **qt2** -- **qt3** - ---- - - - -## function `rotr32` - -```python -rotr32(x, n) -``` - - - - - - ---- - - - -## function `rotl32` - -```python -rotl32(x, n) -``` - - - - - - ---- - - - -## function `byteswap32` - -```python -byteswap32(x) -``` - - - - - - ---- - - - -## function `byte` - -```python -byte(x, n) -``` - - - - - - ---- - - - -## function `qp` - -```python -qp(n, x) -``` - - - - - - ---- - - - -## function `gen_qtab` - -```python -gen_qtab(pkey) -``` - - - - - - ---- - - - -## function `gen_mtab` - -```python -gen_mtab(pkey) -``` - - - - - - ---- - - - -## function `gen_mk_tab` - -```python -gen_mk_tab(pkey, key) -``` - - - - - - ---- - - - -## function `h_fun` - -```python -h_fun(pkey, x, key) -``` - - - - - - ---- - - - -## function `mds_rem` - -```python -mds_rem(p0, p1) -``` - - - - - - ---- - - - -## function `set_key` - -```python -set_key(pkey, in_key, key_len) -``` - - - - - - ---- - - - -## function `encrypt` - -```python -encrypt(pkey, in_blk) -``` - - - - - - ---- - - - -## function `decrypt` - -```python -decrypt(pkey, in_blk) -``` - - - - - - ---- - - - -## class `Twofish` - - - - - - -### method `__init__` - -```python -__init__(key=None) -``` - -Twofish. - - - - ---- - - - -### method `decrypt` - -```python -decrypt(block) -``` - -Decrypt blocks. - ---- - - - -### method `encrypt` - -```python -encrypt(block) -``` - -Encrypt blocks. - ---- - - - -### method `get_block_size` - -```python -get_block_size() -``` - -Get cipher block size in bytes. - ---- - - - -### method `get_key_size` - -```python -get_key_size() -``` - -Get cipher key size in bytes. - ---- - - - -### method `get_name` - -```python -get_name() -``` - -Return the name of the cipher. - ---- - - - -### method `set_key` - -```python -set_key(key) -``` - -Init. - - ---- - - - -## class `TWI` - - - - - - -### method `__init__` - -```python -__init__() -``` - - - - - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/kdbx_parsing.twofish.md b/kdbx_parsing.twofish.md deleted file mode 100644 index aa0020d8..00000000 --- a/kdbx_parsing.twofish.md +++ /dev/null @@ -1,397 +0,0 @@ - - - - -# module `kdbx_parsing.twofish` - - - - -**Global Variables** ---------------- -- **MODE_ECB** -- **MODE_CBC** -- **MODE_CFB** -- **MODE_OFB** -- **MODE_CTR** -- **MODE_XTS** -- **MODE_CMAC** - - ---- - - - -## class `BlockCipher` -Base class for all blockciphers - - - - - -### method `__init__` - -```python -__init__(key, mode, IV, counter, cipher_module, segment_size, args={}) -``` - - - - - - - - ---- - - - -### method `decrypt` - -```python -decrypt(ciphertext, n='') -``` - -Decrypt some ciphertext - - ciphertext = a string of binary data n = the 'tweak' value when the chaining mode is XTS - -The decrypt function will decrypt the supplied ciphertext. The behavior varies slightly depending on the chaining mode. - -ECB, CBC: ---------- When the supplied ciphertext is not a multiple of the blocksize of the cipher, then the remaining ciphertext will be cached. The next time the decrypt function is called with some ciphertext, the new ciphertext will be concatenated to the cache and then cache+ciphertext will be decrypted. - -CFB, OFB, CTR: --------------- When the chaining mode allows the cipher to act as a stream cipher, the decrypt function will always decrypt all of the supplied ciphertext immediately. No cache will be kept. - -XTS: ----- Because the handling of the last two blocks is linked, it needs the whole block of ciphertext to be supplied at once. Every decrypt function called on a XTS cipher will output a decrypted block based on the current supplied ciphertext block. - -CMAC: ------ Mode not supported for decryption as this does not make sense. - ---- - - - -### method `encrypt` - -```python -encrypt(plaintext, n='') -``` - -Encrypt some plaintext - - plaintext = a string of binary data n = the 'tweak' value when the chaining mode is XTS - -The encrypt function will encrypt the supplied plaintext. The behavior varies slightly depending on the chaining mode. - -ECB, CBC: ---------- When the supplied plaintext is not a multiple of the blocksize of the cipher, then the remaining plaintext will be cached. The next time the encrypt function is called with some plaintext, the new plaintext will be concatenated to the cache and then cache+plaintext will be encrypted. - -CFB, OFB, CTR: --------------- When the chaining mode allows the cipher to act as a stream cipher, the encrypt function will always encrypt all of the supplied plaintext immediately. No cache will be kept. - -XTS: ----- Because the handling of the last two blocks is linked, it needs the whole block of plaintext to be supplied at once. Every encrypt function called on a XTS cipher will output an encrypted block based on the current supplied plaintext block. - -CMAC: ------ Everytime the function is called, the hash from the input data is calculated. No finalizing needed. The hashlength is equal to block size of the used block cipher. - ---- - - - -### method `final` - -```python -final(style='pkcs7') -``` - -Finalizes the encryption by padding the cache - - padfct = padding function import from CryptoPlus.Util.padding - -For ECB, CBC: the remaining bytes in the cache will be padded and encrypted. For OFB,CFB, CTR: an encrypted padding will be returned, making the total outputed bytes since construction of the cipher a multiple of the blocksize of that cipher. - -If the cipher has been used for decryption, the final function won't do anything. You have to manually unpad if necessary. - -After finalization, the chain can still be used but the IV, counter etc aren't reset but just continue as they were after the last step (finalization step). - - ---- - - - -## class `CBC` -CBC chaining mode - - - - - -### method `__init__` - -```python -__init__(codebook, blocksize, IV) -``` - - - - - - - - ---- - - - -### method `update` - -```python -update(data, ed) -``` - -Processes the given ciphertext/plaintext - -Inputs: data: raw string of any length ed: 'e' for encryption, 'd' for decryption Output: processed raw string block(s), if any - -When the supplied data is not a multiple of the blocksize of the cipher, then the remaining input data will be cached. The next time the update function is called with some data, the new data will be concatenated to the cache and then cache+data will be processed and full blocks will be outputted. - - ---- - - - -## class `python_Twofish` - - - - - - -### method `__init__` - -```python -__init__(key, mode, IV, counter, segment_size) -``` - - - - - - - - ---- - - - -### method `decrypt` - -```python -decrypt(ciphertext, n='') -``` - -Decrypt some ciphertext - - ciphertext = a string of binary data n = the 'tweak' value when the chaining mode is XTS - -The decrypt function will decrypt the supplied ciphertext. The behavior varies slightly depending on the chaining mode. - -ECB, CBC: ---------- When the supplied ciphertext is not a multiple of the blocksize of the cipher, then the remaining ciphertext will be cached. The next time the decrypt function is called with some ciphertext, the new ciphertext will be concatenated to the cache and then cache+ciphertext will be decrypted. - -CFB, OFB, CTR: --------------- When the chaining mode allows the cipher to act as a stream cipher, the decrypt function will always decrypt all of the supplied ciphertext immediately. No cache will be kept. - -XTS: ----- Because the handling of the last two blocks is linked, it needs the whole block of ciphertext to be supplied at once. Every decrypt function called on a XTS cipher will output a decrypted block based on the current supplied ciphertext block. - -CMAC: ------ Mode not supported for decryption as this does not make sense. - ---- - - - -### method `encrypt` - -```python -encrypt(plaintext, n='') -``` - -Encrypt some plaintext - - plaintext = a string of binary data n = the 'tweak' value when the chaining mode is XTS - -The encrypt function will encrypt the supplied plaintext. The behavior varies slightly depending on the chaining mode. - -ECB, CBC: ---------- When the supplied plaintext is not a multiple of the blocksize of the cipher, then the remaining plaintext will be cached. The next time the encrypt function is called with some plaintext, the new plaintext will be concatenated to the cache and then cache+plaintext will be encrypted. - -CFB, OFB, CTR: --------------- When the chaining mode allows the cipher to act as a stream cipher, the encrypt function will always encrypt all of the supplied plaintext immediately. No cache will be kept. - -XTS: ----- Because the handling of the last two blocks is linked, it needs the whole block of plaintext to be supplied at once. Every encrypt function called on a XTS cipher will output an encrypted block based on the current supplied plaintext block. - -CMAC: ------ Everytime the function is called, the hash from the input data is calculated. No finalizing needed. The hashlength is equal to block size of the used block cipher. - ---- - - - -### method `final` - -```python -final(style='pkcs7') -``` - -Finalizes the encryption by padding the cache - - padfct = padding function import from CryptoPlus.Util.padding - -For ECB, CBC: the remaining bytes in the cache will be padded and encrypted. For OFB,CFB, CTR: an encrypted padding will be returned, making the total outputed bytes since construction of the cipher a multiple of the blocksize of that cipher. - -If the cipher has been used for decryption, the final function won't do anything. You have to manually unpad if necessary. - -After finalization, the chain can still be used but the IV, counter etc aren't reset but just continue as they were after the last step (finalization step). - ---- - - - -### classmethod `new` - -```python -new(key, mode=1, IV=None, counter=None, segment_size=None) -``` - - - - - - ---- - - - -## class `python_Twofish` - - - - - - -### method `__init__` - -```python -__init__(key, mode, IV, counter, segment_size) -``` - - - - - - - - ---- - - - -### method `decrypt` - -```python -decrypt(ciphertext, n='') -``` - -Decrypt some ciphertext - - ciphertext = a string of binary data n = the 'tweak' value when the chaining mode is XTS - -The decrypt function will decrypt the supplied ciphertext. The behavior varies slightly depending on the chaining mode. - -ECB, CBC: ---------- When the supplied ciphertext is not a multiple of the blocksize of the cipher, then the remaining ciphertext will be cached. The next time the decrypt function is called with some ciphertext, the new ciphertext will be concatenated to the cache and then cache+ciphertext will be decrypted. - -CFB, OFB, CTR: --------------- When the chaining mode allows the cipher to act as a stream cipher, the decrypt function will always decrypt all of the supplied ciphertext immediately. No cache will be kept. - -XTS: ----- Because the handling of the last two blocks is linked, it needs the whole block of ciphertext to be supplied at once. Every decrypt function called on a XTS cipher will output a decrypted block based on the current supplied ciphertext block. - -CMAC: ------ Mode not supported for decryption as this does not make sense. - ---- - - - -### method `encrypt` - -```python -encrypt(plaintext, n='') -``` - -Encrypt some plaintext - - plaintext = a string of binary data n = the 'tweak' value when the chaining mode is XTS - -The encrypt function will encrypt the supplied plaintext. The behavior varies slightly depending on the chaining mode. - -ECB, CBC: ---------- When the supplied plaintext is not a multiple of the blocksize of the cipher, then the remaining plaintext will be cached. The next time the encrypt function is called with some plaintext, the new plaintext will be concatenated to the cache and then cache+plaintext will be encrypted. - -CFB, OFB, CTR: --------------- When the chaining mode allows the cipher to act as a stream cipher, the encrypt function will always encrypt all of the supplied plaintext immediately. No cache will be kept. - -XTS: ----- Because the handling of the last two blocks is linked, it needs the whole block of plaintext to be supplied at once. Every encrypt function called on a XTS cipher will output an encrypted block based on the current supplied plaintext block. - -CMAC: ------ Everytime the function is called, the hash from the input data is calculated. No finalizing needed. The hashlength is equal to block size of the used block cipher. - ---- - - - -### method `final` - -```python -final(style='pkcs7') -``` - -Finalizes the encryption by padding the cache - - padfct = padding function import from CryptoPlus.Util.padding - -For ECB, CBC: the remaining bytes in the cache will be padded and encrypted. For OFB,CFB, CTR: an encrypted padding will be returned, making the total outputed bytes since construction of the cipher a multiple of the blocksize of that cipher. - -If the cipher has been used for decryption, the final function won't do anything. You have to manually unpad if necessary. - -After finalization, the chain can still be used but the IV, counter etc aren't reset but just continue as they were after the last step (finalization step). - ---- - - - -### classmethod `new` - -```python -new(key, mode=1, IV=None, counter=None, segment_size=None) -``` - - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/pykeepass.html b/pykeepass.html new file mode 100644 index 00000000..97106631 --- /dev/null +++ b/pykeepass.html @@ -0,0 +1,5474 @@ + + + + + + + pykeepass API documentation + + + + + + + + + +
+
+

+pykeepass

+ +

pykeepass

+ +

+ + +

+ +

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

+ +

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

+ +

Installation

+ +
+
sudo apt install python3-lxml
+pip install pykeepass
+
+
+ +

Quickstart

+ +

General database manipulation

+ +
+
from pykeepass import PyKeePass
+
+# load database
+>>> kp = PyKeePass('db.kdbx', password='somePassw0rd')
+
+# get all entries
+>>> kp.entries
+[Entry: "foo_entry (myusername)", Entry: "foobar_entry (myusername)", ...]
+
+# 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 and OTP information
+>>> entry.password
+'s3cure_p455w0rd'
+>>> entry.otp
+otpauth://totp/test:lkj?secret=TEST%3D%3D%3D%3D&period=30&digits=6&issuer=test
+
+# 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 and manipulating entries

+ +
+
# 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)
+
+# save entry history
+>>> entry.save_history()
+
+
+ +

Finding and manipulating groups

+ +
+
>>> 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: "/"
+
+# 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

+ +
+
>>> 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**
+
+
+ +

OTP 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
+
+
+ +

`

+ +

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

+ +

.. code:: python

+ +

Tests and Debugging

+ +

Run tests with :code:python tests/tests.py or :code: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)"
+
+
+
+
+ + + + + +
 1"""
+ 2.. include:: ../README.md
+ 3"""
+ 4
+ 5from .pykeepass import PyKeePass, create_database
+ 6from .entry import Entry
+ 7from .group import Group
+ 8from .attachment import Attachment
+ 9from .icons import icons as icons
+10from .version import __version__
+11
+12__all__ = ["PyKeePass", "Entry", "Group", "Attachment", "icons", "create_database", "__version__"]
+
+ + +
+
+ +
+ + class + PyKeePass: + + + +
+ +
  38class PyKeePass:
+  39    """Open a KeePass database
+  40
+  41    Args:
+  42        filename (`str`, optional): path to database or stream object.
+  43            If None, the path given when the database was opened is used.
+  44        password (`str`, optional): database password.  If None,
+  45            database is assumed to have no password
+  46        keyfile (`str`, optional): path to keyfile.  If None,
+  47            database is assumed to have no keyfile
+  48        transformed_key (`bytes`, optional): precomputed transformed
+  49            key.
+  50        decrypt (`bool`, optional): whether to decrypt XML payload.
+  51            Set `False` to access outer header information without decrypting
+  52            database.
+  53
+  54    Raises:
+  55        `CredentialsError`: raised when password/keyfile or transformed key
+  56            are wrong
+  57        `HeaderChecksumError`: raised when checksum in database header is
+  58            is wrong.  e.g. database tampering or file corruption
+  59        `PayloadChecksumError`: raised when payload blocks checksum is wrong,
+  60            e.g. corruption during database saving
+  61
+  62    """
+  63
+  64    # TODO: raise, no filename provided, database not open
+  65
+  66    def __init__(self, filename, password=None, keyfile=None,
+  67                 transformed_key=None, decrypt=True):
+  68
+  69        self.read(
+  70            filename=filename,
+  71            password=password,
+  72            keyfile=keyfile,
+  73            transformed_key=transformed_key,
+  74            decrypt=decrypt
+  75        )
+  76
+  77    def __enter__(self):
+  78        return self
+  79
+  80    def __exit__(self, typ, value, tb):
+  81        # see issue 137
+  82        pass
+  83
+  84    def read(self, filename=None, password=None, keyfile=None,
+  85             transformed_key=None, decrypt=True):
+  86        """
+  87        See class docstring.
+  88        """
+  89
+  90        # TODO: - raise, no filename provided, database not open
+  91        self._password = password
+  92        self._keyfile = keyfile
+  93        if filename:
+  94            self.filename = filename
+  95        else:
+  96            filename = self.filename
+  97
+  98        try:
+  99            if hasattr(filename, "read"):
+ 100                self.kdbx = KDBX.parse_stream(
+ 101                    filename,
+ 102                    password=password,
+ 103                    keyfile=keyfile,
+ 104                    transformed_key=transformed_key,
+ 105                    decrypt=decrypt
+ 106                )
+ 107            else:
+ 108                self.kdbx = KDBX.parse_file(
+ 109                    filename,
+ 110                    password=password,
+ 111                    keyfile=keyfile,
+ 112                    transformed_key=transformed_key,
+ 113                    decrypt=decrypt
+ 114                )
+ 115
+ 116        except CheckError as e:
+ 117            if e.path == '(parsing) -> header -> sig_check':
+ 118                raise HeaderChecksumError("Not a KeePass database")
+ 119            else:
+ 120                raise
+ 121
+ 122        # body integrity/verification
+ 123        except ChecksumError as e:
+ 124            if e.path in (
+ 125                    '(parsing) -> body -> cred_check', # KDBX4
+ 126                    '(parsing) -> cred_check' # KDBX3
+ 127                    ):
+ 128                raise CredentialsError("Invalid credentials")
+ 129            elif e.path == '(parsing) -> body -> sha256':
+ 130                raise HeaderChecksumError("Corrupted database")
+ 131            elif e.path in (
+ 132                    '(parsing) -> body -> payload -> hmac_hash', # KDBX4
+ 133                    '(parsing) -> xml -> block_hash' # KDBX3
+ 134                    ):
+ 135                raise PayloadChecksumError("Error reading database contents")
+ 136            else:
+ 137                raise
+ 138
+ 139    def reload(self):
+ 140        """Reload current database using previously given credentials """
+ 141
+ 142        self.read(self.filename, self.password, self.keyfile)
+ 143
+ 144    def save(self, filename=None, transformed_key=None):
+ 145        """Save current database object to disk.
+ 146
+ 147        Args:
+ 148            filename (`str`, optional): path to database or stream object.
+ 149                If None, the path given when the database was opened is used.
+ 150                PyKeePass.filename is unchanged.
+ 151            transformed_key (`bytes`, optional): precomputed transformed
+ 152                key.
+ 153        """
+ 154
+ 155        if not filename:
+ 156            filename = self.filename
+ 157
+ 158        if hasattr(filename, "write"):
+ 159            KDBX.build_stream(
+ 160                self.kdbx,
+ 161                filename,
+ 162                password=self.password,
+ 163                keyfile=self.keyfile,
+ 164                transformed_key=transformed_key,
+ 165                decrypt=True
+ 166            )
+ 167        else:
+ 168            # save to temporary file to prevent database clobbering
+ 169            # see issues 223, 101
+ 170            filename_tmp = Path(filename).with_suffix('.tmp')
+ 171            try:
+ 172                KDBX.build_file(
+ 173                    self.kdbx,
+ 174                    filename_tmp,
+ 175                    password=self.password,
+ 176                    keyfile=self.keyfile,
+ 177                    transformed_key=transformed_key,
+ 178                    decrypt=True
+ 179                )
+ 180            except Exception as e:
+ 181                os.remove(filename_tmp)
+ 182                raise e
+ 183            shutil.move(filename_tmp, filename)
+ 184
+ 185    @property
+ 186    def version(self):
+ 187        """`tuple` of `int`: Length 2 tuple of ints containing major and minor versions.
+ 188        Generally (3, 1) or (4, 0)."""
+ 189        return (
+ 190            self.kdbx.header.value.major_version,
+ 191            self.kdbx.header.value.minor_version
+ 192        )
+ 193
+ 194    @property
+ 195    def encryption_algorithm(self):
+ 196        """`str`: encryption algorithm used by database during decryption.
+ 197        Can be one of 'aes256', 'chacha20', or 'twofish'."""
+ 198        return self.kdbx.header.value.dynamic_header.cipher_id.data
+ 199
+ 200    @property
+ 201    def kdf_algorithm(self):
+ 202        """`str`: key derivation algorithm used by database during decryption.
+ 203        Can be one of 'aeskdf', 'argon2', or 'aeskdf'"""
+ 204        if self.version == (3, 1):
+ 205            return 'aeskdf'
+ 206        elif self.version == (4, 0):
+ 207            kdf_parameters = self.kdbx.header.value.dynamic_header.kdf_parameters.data.dict
+ 208            if kdf_parameters['$UUID'].value == kdf_uuids['argon2']:
+ 209                return 'argon2'
+ 210            elif kdf_parameters['$UUID'].value == kdf_uuids['argon2id']:
+ 211                return 'argon2id'
+ 212            elif kdf_parameters['$UUID'].value == kdf_uuids['aeskdf']:
+ 213                return 'aeskdf'
+ 214
+ 215    @property
+ 216    def transformed_key(self):
+ 217        """`bytes`: transformed key used in database decryption.  May be cached
+ 218        and passed to `open` for faster database opening"""
+ 219        return self.kdbx.body.transformed_key
+ 220
+ 221    @property
+ 222    def database_salt(self):
+ 223       """`bytes`: salt of database kdf. This can be used for adding additional
+ 224       credentials which are used in extension to current keyfile."""
+ 225
+ 226       if self.version == (3, 1):
+ 227            return self.kdbx.header.value.dynamic_header.transform_seed.data
+ 228
+ 229       kdf_parameters = self.kdbx.header.value.dynamic_header.kdf_parameters.data.dict
+ 230       return kdf_parameters['S'].value
+ 231
+ 232    @property
+ 233    def payload(self):
+ 234        """`construct.Container`: Encrypted payload of keepass database"""
+ 235
+ 236        # check if payload is decrypted
+ 237        if self.kdbx.body.payload is None:
+ 238            raise ValueError("Database is not decrypted")
+ 239        else:
+ 240            return self.kdbx.body.payload
+ 241
+ 242    @property
+ 243    def tree(self):
+ 244        """`lxml.etree._ElementTree`: database XML payload"""
+ 245        return self.payload.xml
+ 246
+ 247    @property
+ 248    def root_group(self):
+ 249        """`Group`: root Group of database"""
+ 250        return self.find_groups(path='', first=True)
+ 251
+ 252    @property
+ 253    def recyclebin_group(self):
+ 254        """`Group`: RecycleBin Group of database"""
+ 255        elem = self._xpath('/KeePassFile/Meta/RecycleBinUUID', first=True)
+ 256        recyclebin_uuid = uuid.UUID( bytes = base64.b64decode(elem.text) )
+ 257        return self.find_groups(uuid=recyclebin_uuid, first=True)
+ 258
+ 259    @property
+ 260    def groups(self):
+ 261        """`list` of `Group`: all groups in database
+ 262        """
+ 263        return self.find_groups()
+ 264
+ 265    @property
+ 266    def entries(self):
+ 267        """`list` of `Entry`: all entries in database,
+ 268        excluding history"""
+ 269        return self.find_entries()
+ 270
+ 271    @property
+ 272    def database_name(self):
+ 273        """`str`: Name of database"""
+ 274        elem = self._xpath('/KeePassFile/Meta/DatabaseName', first=True)
+ 275        return elem.text
+ 276
+ 277    @database_name.setter
+ 278    def database_name(self, name):
+ 279        item = self._xpath('/KeePassFile/Meta/DatabaseName', first=True)
+ 280        item.text = str(name)
+ 281
+ 282    @property
+ 283    def database_description(self):
+ 284        """`str`: Description of the database"""
+ 285        elem = self._xpath('/KeePassFile/Meta/DatabaseDescription', first=True)
+ 286        return elem.text
+ 287
+ 288    @database_description.setter
+ 289    def database_description(self, name):
+ 290        item = self._xpath('/KeePassFile/Meta/DatabaseDescription', first=True)
+ 291        item.text = str(name)
+ 292
+ 293    @property
+ 294    def default_username(self):
+ 295        """`str` or `None`: default user.  `None` if not set"""
+ 296        elem = self._xpath('/KeePassFile/Meta/DefaultUserName', first=True)
+ 297        return elem.text
+ 298
+ 299    @default_username.setter
+ 300    def default_username(self, name):
+ 301        item = self._xpath('/KeePassFile/Meta/DefaultUserName', first=True)
+ 302        item.text = str(name)
+ 303
+ 304    def xml(self):
+ 305        """Get XML part of database as string
+ 306
+ 307        Returns:
+ 308            `str`: XML content of database
+ 309        """
+ 310        return etree.tostring(
+ 311            self.tree,
+ 312            pretty_print=True,
+ 313            standalone=True,
+ 314            encoding='utf-8'
+ 315        )
+ 316
+ 317    def dump_xml(self, filename):
+ 318        """ Dump the contents of the database to file as XML
+ 319
+ 320        Args:
+ 321            filename (`str`): path to output file
+ 322        """
+ 323        with open(filename, 'wb') as f:
+ 324            f.write(self.xml())
+ 325
+ 326    def xpath(self, xpath_str, tree=None, first=False, cast=False, **kwargs):
+ 327        """Look up elements in the XML payload and return corresponding object.
+ 328
+ 329        Internal function which searches the payload lxml ElementTree for
+ 330        elements via XPath.  Matched entry, group, and attachment elements are
+ 331        automatically cast to their corresponding objects, otherwise an error
+ 332        is raised.
+ 333
+ 334        Args:
+ 335            xpath_str (`str`): XPath query for finding element(s)
+ 336            tree (`_ElementTree`, `Element`, optional): use this
+ 337                element as root node when searching
+ 338            first (`bool`): If True, function returns first result or None.  If
+ 339                False, function returns list of matches or empty list.
+ 340                    (default `False`).
+ 341            cast (`bool`): If True, matches are instead instantiated as
+ 342                pykeepass Group, Entry, or Attachment objects.  An exception
+ 343                is raised if a match cannot be cast.  (default `False`)
+ 344
+ 345        Returns:
+ 346            `list` of `Group`, `Entry`, `Attachment`, or `lxml.etree.Element`
+ 347        """
+ 348
+ 349        if tree is None:
+ 350            tree = self.tree
+ 351        logger.debug('xpath query: ' + xpath_str)
+ 352        elements = tree.xpath(
+ 353            xpath_str, namespaces={'re': 'http://exslt.org/regular-expressions'}
+ 354        )
+ 355
+ 356        res = []
+ 357        for e in elements:
+ 358            if cast:
+ 359                if e.tag == 'Entry':
+ 360                    res.append(Entry(element=e, kp=self))
+ 361                elif e.tag == 'Group':
+ 362                    res.append(Group(element=e, kp=self))
+ 363                elif e.tag == 'Binary' and e.getparent().tag == 'Entry':
+ 364                    res.append(Attachment(element=e, kp=self))
+ 365                else:
+ 366                    raise Exception('Could not cast element {}'.format(e))
+ 367            else:
+ 368                res.append(e)
+ 369
+ 370        # return first object in list or None
+ 371        if first:
+ 372            res = res[0] if res else None
+ 373
+ 374        return res
+ 375
+ 376    _xpath = xpath
+ 377
+ 378    def _find(self, prefix, keys_xp, path=None, tree=None, first=False,
+ 379              history=False, regex=False, flags=None, **kwargs):
+ 380        """Internal function for converting a search into an XPath string"""
+ 381
+ 382        xp = ''
+ 383
+ 384        if not history:
+ 385            prefix += '[not(ancestor::History)]'
+ 386
+ 387        if path is not None:
+ 388
+ 389            first = True
+ 390
+ 391            xp += '/KeePassFile/Root/Group'
+ 392            # split provided path into group and element
+ 393            group_path = path[:-1]
+ 394            element = path[-1] if len(path) > 0 else ''
+ 395            # build xpath from group_path and element
+ 396            for group in group_path:
+ 397                xp += path_xp[regex]['group'].format(group, flags=flags)
+ 398            if 'Entry' in prefix:
+ 399                xp += path_xp[regex]['entry'].format(element, flags=flags)
+ 400            elif element and 'Group' in prefix:
+ 401                xp += path_xp[regex]['group'].format(element, flags=flags)
+ 402
+ 403        else:
+ 404            if tree is not None:
+ 405                xp += '.'
+ 406
+ 407            xp += prefix
+ 408
+ 409            # handle searching custom string fields
+ 410            if 'string' in kwargs:
+ 411                for key, value in kwargs['string'].items():
+ 412                    xp += keys_xp[regex]['string'].format(key, value, flags=flags)
+ 413
+ 414                kwargs.pop('string')
+ 415
+ 416            # convert uuid to base64 form before building xpath
+ 417            if 'uuid' in kwargs:
+ 418                kwargs['uuid'] = base64.b64encode(kwargs['uuid'].bytes).decode('utf-8')
+ 419
+ 420            # convert tags to semicolon separated string before building xpath
+ 421            # FIXME: this isn't a reliable way to search tags.  e.g. searching ['tag1', 'tag2'] will match 'tag1tag2
+ 422            if 'tags' in kwargs:
+ 423                kwargs['tags'] = ' and '.join(f'contains(text(),"{t}")' for t in kwargs['tags'])
+ 424
+ 425            # build xpath to filter results with specified attributes
+ 426            for key, value in kwargs.items():
+ 427                if key not in keys_xp[regex]:
+ 428                    raise TypeError('Invalid keyword argument "{}"'.format(key))
+ 429                if value is not None:
+ 430                    xp += keys_xp[regex][key].format(value, flags=flags)
+ 431
+ 432        res = self._xpath(
+ 433            xp,
+ 434            tree=tree._element if tree else None,
+ 435            first=first,
+ 436            cast=True,
+ 437            **kwargs
+ 438        )
+ 439
+ 440        return res
+ 441
+ 442    def _can_be_moved_to_recyclebin(self, entry_or_group):
+ 443        if entry_or_group == self.root_group:
+ 444            return False
+ 445        recyclebin_group = self.recyclebin_group
+ 446        if recyclebin_group is None:
+ 447            return True
+ 448        uuid_str = base64.b64encode( entry_or_group.uuid.bytes).decode('utf-8')
+ 449        elem = self._xpath('./UUID[text()="{}"]/..'.format(uuid_str), tree=recyclebin_group._element, first=True, cast=False)
+ 450        return elem is None
+ 451
+ 452
+ 453    # ---------- Groups ----------
+ 454
+ 455    from .deprecated import (
+ 456        find_groups_by_name,
+ 457        find_groups_by_notes,
+ 458        find_groups_by_path,
+ 459        find_groups_by_uuid,
+ 460    )
+ 461
+ 462    def find_groups(self, recursive=True, path=None, group=None, **kwargs):
+ 463        """ Find groups in a database
+ 464
+ 465        [XSLT style]: https://www.xml.com/pub/a/2003/06/04/tr.html
+ 466        [flags]: https://www.w3.org/TR/xpath-functions/#flags
+ 467
+ 468        Args:
+ 469            name (`str`): name of group
+ 470            first (`bool`): return first result instead of list (default `False`)
+ 471            recursive (`bool`): do a recursive search of all groups/subgroups
+ 472            path (`list` of `str`): do group search starting from path
+ 473            group (`Group`): search underneath group
+ 474            uuid (`uuid.UUID`): group UUID
+ 475            regex (`bool`): whether `str` search arguments contain [XSLT style][XSLT style] regular expression
+ 476            flags (`str`): XPath [flags][flags]
+ 477
+ 478        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.
+ 479
+ 480        The `group` argument determines what `Group` to search under, and the `recursive` boolean controls whether to search recursively.
+ 481
+ 482        The `first` (default `False`) boolean controls whether to return the first matched item, or a list of matched items.
+ 483
+ 484        - if `first=False`, the function returns a list of `Group` or `[]` if there are no matches
+ 485        - if `first=True`, the function returns the first `Group` match, or `None` if there are no matches
+ 486
+ 487        Returns:
+ 488            `list` of `Group` if `first=False`
+ 489            or (`Group` or `None`) if `first=True`
+ 490
+ 491        Examples:
+ 492        ``` python
+ 493        >>> kp.find_groups(name='foo', first=True)
+ 494        Group: "foo"
+ 495
+ 496        >>> kp.find_groups(name='foo.*', regex=True)
+ 497        [Group: "foo", Group "foobar"]
+ 498
+ 499        >>> kp.find_groups(path=['social'], regex=True)
+ 500        [Group: "social", Group: "social/foo_subgroup"]
+ 501
+ 502        >>> kp.find_groups(name='social', first=True).subgroups
+ 503        [Group: "social/foo_subgroup"]
+ 504        ```
+ 505        """
+ 506
+ 507        prefix = '//Group' if recursive else '/Group'
+ 508        res = self._find(prefix, group_xp, path=path, tree=group, **kwargs)
+ 509        return res
+ 510
+ 511    def add_group(self, destination_group, group_name, icon=None, notes=None):
+ 512        """Create a new group and all parent groups, if necessary
+ 513
+ 514        Args:
+ 515            destination_group (`Group`): parent group to add a new group to
+ 516            group_name (`str`): name of new group
+ 517            icon (`str`): icon name from `icons`
+ 518            notes (`str`): group notes
+ 519
+ 520        Returns:
+ 521            `Group`: newly added group
+ 522        """
+ 523        logger.debug('Creating group {}'.format(group_name))
+ 524
+ 525        if icon:
+ 526            group = Group(name=group_name, icon=icon, notes=notes, kp=self)
+ 527        else:
+ 528            group = Group(name=group_name, notes=notes, kp=self)
+ 529        destination_group.append(group)
+ 530
+ 531        return group
+ 532
+ 533    def delete_group(self, group):
+ 534        group.delete()
+ 535
+ 536    def move_group(self, group, destination_group):
+ 537        """Move a group"""
+ 538        destination_group.append(group)
+ 539
+ 540    def _create_or_get_recyclebin_group(self, **kwargs):
+ 541        existing_group = self.recyclebin_group
+ 542        if existing_group is not None:
+ 543            return existing_group
+ 544        kwargs.setdefault('group_name', 'Recycle Bin')
+ 545        group = self.add_group( self.root_group, **kwargs)
+ 546        elem = self._xpath('/KeePassFile/Meta/RecycleBinUUID', first=True)
+ 547        elem.text = base64.b64encode(group.uuid.bytes).decode('utf-8')
+ 548        return group
+ 549
+ 550    def trash_group(self, group):
+ 551        """Move a group to the RecycleBin
+ 552
+ 553        The recycle bin is created if it does not exit. ``group`` must be an empty Group.
+ 554
+ 555        Args:
+ 556            group (`Group`): Group to send to the RecycleBin
+ 557        """
+ 558        if not self._can_be_moved_to_recyclebin(group):
+ 559            raise UnableToSendToRecycleBin
+ 560        recyclebin_group = self._create_or_get_recyclebin_group()
+ 561        self.move_group( group, recyclebin_group)
+ 562
+ 563    def empty_group(self, group):
+ 564        """Delete all entries and subgroups of a group.
+ 565
+ 566        This does not delete the group itself
+ 567
+ 568        Args:
+ 569            group (`Group`): Group to empty
+ 570        """
+ 571        while len(group.subgroups):
+ 572            self.delete_group(group.subgroups[0])
+ 573        while len(group.entries):
+ 574            self.delete_entry(group.entries[0])
+ 575
+ 576    # ---------- Entries ----------
+ 577
+ 578
+ 579    from .deprecated import (
+ 580        find_entries_by_notes,
+ 581        find_entries_by_password,
+ 582        find_entries_by_path,
+ 583        find_entries_by_string,
+ 584        find_entries_by_title,
+ 585        find_entries_by_url,
+ 586        find_entries_by_username,
+ 587        find_entries_by_uuid,
+ 588    )
+ 589
+ 590    def find_entries(self, recursive=True, path=None, group=None, **kwargs):
+ 591        """Returns entries which match all provided parameters
+ 592        Args:
+ 593            path (`list` of (`str` or `None`), optional): full path to an entry
+ 594                (eg. `['foobar_group', 'foobar_entry']`).  This implies `first=True`.
+ 595                All other arguments are ignored when this is given.  This is useful for
+ 596                handling user input.
+ 597            title (`str`, optional): title of entry to find
+ 598            username (`str`, optional): username of entry to find
+ 599            password (`str`, optional): password of entry to find
+ 600            url (`str`, optional): url of entry to find
+ 601            notes (`str`, optional): notes of entry to find
+ 602            otp (`str`, optional): otp string of entry to find
+ 603            string (`dict`): custom string fields.
+ 604                (eg. `{'custom_field1': 'custom value', 'custom_field2': 'custom value'}`)
+ 605            uuid (`uuid.UUID`): entry UUID
+ 606            tags (`list` of `str`): entry tags
+ 607            autotype_enabled (`bool`, optional): autotype string is enabled
+ 608            autotype_sequence (`str`, optional): autotype string
+ 609            autotype_window (`str`, optional): autotype target window filter string
+ 610            group (`Group` or `None`, optional): search under this group
+ 611            first (`bool`, optional): return first match or `None` if no matches.
+ 612                Otherwise return list of `Entry` matches. (default `False`)
+ 613            history (`bool`): include history entries in results. (default `False`)
+ 614            recursive (`bool`): search recursively
+ 615            regex (`bool`): interpret search strings given above as
+ 616                [XSLT style](https://www.xml.com/pub/a/2003/06/04/tr.html) regexes
+ 617            flags (`str`): regex [search flags](https://www.w3.org/TR/xpath-functions/#flags)
+ 618
+ 619        Returns:
+ 620            `list` of `Entry` if `first=False`
+ 621            or (`Entry` or `None`) if `first=True`
+ 622
+ 623        Examples:
+ 624
+ 625        ``` python
+ 626        >>> kp.find_entries(title='gmail', first=True)
+ 627        Entry: "social/gmail (myusername)"
+ 628
+ 629        >>> kp.find_entries(title='foo.*', regex=True)
+ 630        [Entry: "foo_entry (myusername)", Entry: "foobar_entry (myusername)"]
+ 631
+ 632        >>> entry = kp.find_entries(title='foo.*', url='.*facebook.*', regex=True, first=True)
+ 633        >>> entry.url
+ 634        'facebook.com'
+ 635        >>> entry.title
+ 636        'foo_entry'
+ 637        >>> entry.title = 'hello'
+ 638
+ 639        >>> group = kp.find_group(name='social', first=True)
+ 640        >>> kp.find_entries(title='facebook', group=group, recursive=False, first=True)
+ 641        Entry: "social/facebook (myusername)"
+ 642        ```
+ 643        """
+ 644
+ 645        prefix = '//Entry' if recursive else '/Entry'
+ 646        res = self._find(prefix, entry_xp, path=path, tree=group, **kwargs)
+ 647
+ 648        return res
+ 649
+ 650
+ 651    def add_entry(self, destination_group, title, username,
+ 652                  password, url=None, notes=None, expiry_time=None,
+ 653                  tags=None, otp=None, icon=None, force_creation=False):
+ 654
+ 655        """Create a new entry
+ 656
+ 657        Args:
+ 658            destination_group (`Group`): parent group to add a new entry to
+ 659            title (`str`, or `None`): title of new entry
+ 660            username (`str` or `None`): username of new entry
+ 661            password (`str` or `None`): password of new entry
+ 662            url (`str` or `None`): URL of new entry
+ 663            notes (`str` or `None`): notes of new entry
+ 664            expiry_time (`datetime.datetime`): time of entry expiration
+ 665            tags (`list` of `str` or `None`): entry tags
+ 666            otp (`str` or `None`): OTP code of object
+ 667            icon (`str`, optional): icon name from `icons`
+ 668            force_creation (`bool`): create entry even if one with identical
+ 669                title exists in this group (default `False`)
+ 670
+ 671        If ``expiry_time`` is a naive datetime object
+ 672        (i.e. ``expiry_time.tzinfo`` is not set), the timezone is retrieved from
+ 673        ``dateutil.tz.gettz()``.
+ 674
+ 675
+ 676        Returns:
+ 677            `Group`: newly added group
+ 678        """
+ 679
+ 680        entries = self.find_entries(
+ 681            title=title,
+ 682            username=username,
+ 683            first=True,
+ 684            group=destination_group,
+ 685            recursive=False
+ 686        )
+ 687
+ 688        if entries and not force_creation:
+ 689            raise Exception(
+ 690                'An entry "{}" already exists in "{}"'.format(
+ 691                    title, destination_group
+ 692                )
+ 693            )
+ 694        else:
+ 695            logger.debug('Creating a new entry')
+ 696            entry = Entry(
+ 697                title=title,
+ 698                username=username,
+ 699                password=password,
+ 700                notes=notes,
+ 701                otp=otp,
+ 702                url=url,
+ 703                tags=tags,
+ 704                expires=True if expiry_time else False,
+ 705                expiry_time=expiry_time,
+ 706                icon=icon,
+ 707                kp=self
+ 708            )
+ 709            destination_group.append(entry)
+ 710
+ 711        return entry
+ 712
+ 713    def delete_entry(self, entry):
+ 714        """Delete entry
+ 715
+ 716        Args:
+ 717            entry (`Entry`): entry to delete
+ 718        """
+ 719        entry.delete()
+ 720
+ 721    def move_entry(self, entry, destination_group):
+ 722        """Move entry to group
+ 723
+ 724        Args:
+ 725            entry (`Entry`): entry to move
+ 726            destination_group (`Group`): group to move to
+ 727        """
+ 728        destination_group.append(entry)
+ 729
+ 730    def trash_entry(self, entry):
+ 731        """Move an entry to the RecycleBin
+ 732
+ 733        The recycle bin is created if it does not exit.
+ 734
+ 735        Args:
+ 736            entry (`Entry`): Entry to send to the RecycleBin
+ 737        """
+ 738        if not self._can_be_moved_to_recyclebin(entry):
+ 739            raise UnableToSendToRecycleBin
+ 740        recyclebin_group = self._create_or_get_recyclebin_group()
+ 741        self.move_entry( entry, recyclebin_group)
+ 742
+ 743    # ---------- Attachments ----------
+ 744
+ 745    def find_attachments(self, recursive=True, path=None, element=None, **kwargs):
+ 746        """ Find attachments in database
+ 747
+ 748        Args:
+ 749            id (`int` or `None`): attachment ID to match
+ 750            filename (`str` or `None`): filename to match
+ 751            element (`Entry` or `Group` or `None`): entry or group to search under
+ 752            recursive (`bool`): search recursively (default `True`)
+ 753            regex (`bool`): whether `str` search arguments contain [XSLT style][XSLT style] regular expression
+ 754            flags (`str`): XPath [flags][flags]
+ 755            history (`bool`): search under history entries. (default `False`)
+ 756            first (`bool`): If True, function returns first result or None.  If
+ 757                False, function returns list of matches or empty list.  (default
+ 758                `False`).
+ 759        """
+ 760
+ 761        prefix = '//Binary' if recursive else '/Binary'
+ 762        res = self._find(prefix, attachment_xp, path=path, tree=element, **kwargs)
+ 763
+ 764        return res
+ 765
+ 766    @property
+ 767    def attachments(self):
+ 768        """`list` of `Attachment`: all attachments in database"""
+ 769        return self.find_attachments(filename='.*', regex=True)
+ 770
+ 771    @property
+ 772    def binaries(self):
+ 773        """`list` of `bytes`: all attachment binaries in database.  The position
+ 774        within this list indicates the binary's ID"""
+ 775        if self.version >= (4, 0):
+ 776            # first byte is a prepended flag
+ 777            binaries = [a.data[1:] for a in self.payload.inner_header.binary]
+ 778        else:
+ 779            binaries = []
+ 780            for elem in self._xpath('/KeePassFile/Meta/Binaries/Binary'):
+ 781                if elem.text is not None:
+ 782                    if elem.get('Compressed') == 'True':
+ 783                        data = zlib.decompress(
+ 784                            base64.b64decode(elem.text),
+ 785                            zlib.MAX_WBITS | 32
+ 786                        )
+ 787                    else:
+ 788                        data = base64.b64decode(elem.text)
+ 789                else:
+ 790                    data = b''
+ 791                binaries.insert(int(elem.attrib['ID']), data)
+ 792
+ 793        return binaries
+ 794
+ 795    def add_binary(self, data, compressed=True, protected=True):
+ 796        """Add binary data to database.  Note this does not create an attachment (see `Entry.add_attachment`)
+ 797
+ 798        Args:
+ 799            data (`bytes`): binary data
+ 800            compressed (`bool`): whether binary data should be compressed.
+ 801                (default `True`).  Applies only to KDBX3
+ 802            protected (`bool`): whether protected flag should be set.  (default `True`).  Note
+ 803                Applies only to KDBX4
+ 804
+ 805        Returns:
+ 806            id (`int`): ID of binary in database
+ 807        """
+ 808        if self.version >= (4, 0):
+ 809            # add protected flag byte
+ 810            data = b'\x01' + data if protected else b'\x00' + data
+ 811            # add binary element to inner header
+ 812            c = Container(type='binary', data=data)
+ 813            self.payload.inner_header.binary.append(c)
+ 814        else:
+ 815            binaries = self._xpath(
+ 816                '/KeePassFile/Meta/Binaries',
+ 817                first=True
+ 818            )
+ 819            if compressed:
+ 820                # gzip compression
+ 821                compressor = zlib.compressobj(
+ 822                    zlib.Z_DEFAULT_COMPRESSION,
+ 823                    zlib.DEFLATED,
+ 824                    zlib.MAX_WBITS | 16
+ 825                )
+ 826                data = compressor.compress(data)
+ 827                data += compressor.flush()
+ 828            data = base64.b64encode(data).decode()
+ 829
+ 830            # set ID for Binary Element
+ 831            binary_id = len(self.binaries)
+ 832
+ 833            # add binary element to XML
+ 834            binaries.append(
+ 835                E.Binary(data, ID=str(binary_id), Compressed=str(compressed))
+ 836            )
+ 837
+ 838        # return binary id
+ 839        return len(self.binaries) - 1
+ 840
+ 841    def delete_binary(self, id):
+ 842        """Remove a binary from database and deletes attachments that reference it
+ 843
+ 844        Since attachments reference binaries by their positional index,
+ 845        attachments that reference binaries with ID > `id` will automatically be decremented
+ 846
+ 847        Args:
+ 848            id (`int`): ID of binary to remove
+ 849
+ 850        Raises:
+ 851            `IndexError`: raised when binary with given ID does not exist
+ 852        """
+ 853        try:
+ 854            if self.version >= (4, 0):
+ 855                # remove binary element from inner header
+ 856                self.payload.inner_header.binary.pop(id)
+ 857            else:
+ 858                # remove binary element from XML
+ 859                binaries = self._xpath('/KeePassFile/Meta/Binaries', first=True)
+ 860                binaries.remove(binaries.getchildren()[id])
+ 861        except IndexError:
+ 862            raise BinaryError('No such binary with id {}'.format(id))
+ 863
+ 864        # remove all entry references to this attachment
+ 865        for reference in self.find_attachments(id=id):
+ 866            reference.delete()
+ 867
+ 868        # decrement references greater than this id
+ 869        binaries_gt = self._xpath(
+ 870            '//Binary/Value[@Ref > "{}"]/..'.format(id),
+ 871            cast=True
+ 872        )
+ 873        for reference in binaries_gt:
+ 874            reference.id = reference.id - 1
+ 875
+ 876    # ---------- Misc ----------
+ 877
+ 878    def deref(self, value):
+ 879
+ 880        """Dereference [field reference][fieldref] of Entry
+ 881
+ 882        Args:
+ 883            ref (`str`): KeePass reference string to another field
+ 884
+ 885        Returns:
+ 886            `str`, `uuid.UUID` or `None` if no match found
+ 887
+ 888        [fieldref]: https://keepass.info/help/base/fieldrefs.html
+ 889        """
+ 890        if not value:
+ 891            return value
+ 892        references = set(re.findall(r'({REF:([TUPANI])@([TUPANI]):([^}]+)})', value))
+ 893        if not references:
+ 894            return value
+ 895        field_to_attribute = {
+ 896            'T': 'title',
+ 897            'U': 'username',
+ 898            'P': 'password',
+ 899            'A': 'url',
+ 900            'N': 'notes',
+ 901            'I': 'uuid',
+ 902        }
+ 903        for ref, wanted_field, search_in, search_value in references:
+ 904            wanted_field = field_to_attribute[wanted_field]
+ 905            search_in = field_to_attribute[search_in]
+ 906            if search_in == 'uuid':
+ 907                search_value = uuid.UUID(search_value)
+ 908            ref_entry = self.find_entries(first=True, **{search_in: search_value})
+ 909            if ref_entry is None:
+ 910                return None
+ 911            value = value.replace(ref, getattr(ref_entry, wanted_field))
+ 912        return self.deref(value)
+ 913
+ 914
+ 915    # ---------- Credential Changing and Expiry ----------
+ 916
+ 917    @property
+ 918    def password(self):
+ 919        """`str`: Get or set database password"""
+ 920        return self._password
+ 921
+ 922    @password.setter
+ 923    def password(self, password):
+ 924        self._password = password
+ 925        self.credchange_date = datetime.now(timezone.utc)
+ 926
+ 927    @property
+ 928    def keyfile(self):
+ 929        """`str` or `pathlib.Path`: get or set database keyfile"""
+ 930        return self._keyfile
+ 931
+ 932    @keyfile.setter
+ 933    def keyfile(self, keyfile):
+ 934        self._keyfile = keyfile
+ 935        self.credchange_date = datetime.now(timezone.utc)
+ 936
+ 937    @property
+ 938    def credchange_required_days(self):
+ 939        """`int`: Days until password update should be required"""
+ 940        e = self._xpath('/KeePassFile/Meta/MasterKeyChangeForce', first=True)
+ 941        if e is not None:
+ 942            return int(e.text)
+ 943
+ 944    @property
+ 945    def credchange_recommended_days(self):
+ 946        """`int`: Days until password update should be recommended"""
+ 947        e = self._xpath('/KeePassFile/Meta/MasterKeyChangeRec', first=True)
+ 948        if e is not None:
+ 949            return int(e.text)
+ 950
+ 951    @credchange_required_days.setter
+ 952    def credchange_required_days(self, days):
+ 953        path = '/KeePassFile/Meta/MasterKeyChangeForce'
+ 954        item = self._xpath(path, first=True)
+ 955        item.text = str(days)
+ 956
+ 957    @credchange_recommended_days.setter
+ 958    def credchange_recommended_days(self, days):
+ 959        path = '/KeePassFile/Meta/MasterKeyChangeRec'
+ 960        item = self._xpath(path, first=True)
+ 961        item.text = str(days)
+ 962
+ 963    @property
+ 964    def credchange_date(self):
+ 965        """`datetime.datetime`: get or set UTC time of last credential change"""
+ 966        e = self._xpath('/KeePassFile/Meta/MasterKeyChanged', first=True)
+ 967        if e is not None:
+ 968            return self._decode_time(e.text)
+ 969
+ 970    @credchange_date.setter
+ 971    def credchange_date(self, date):
+ 972        mk_time = self._xpath('/KeePassFile/Meta/MasterKeyChanged', first=True)
+ 973        mk_time.text = self._encode_time(date)
+ 974
+ 975    @property
+ 976    def credchange_required(self):
+ 977        """`bool`: Check if credential change is required"""
+ 978        change_date = self.credchange_date
+ 979        if change_date is None or self.credchange_required_days == -1:
+ 980            return False
+ 981        now_date = datetime.now(timezone.utc)
+ 982        return (now_date - change_date).days > self.credchange_required_days
+ 983
+ 984    @property
+ 985    def credchange_recommended(self):
+ 986        """`bool`: Check if credential change is recommended"""
+ 987        change_date = self.credchange_date
+ 988        if change_date is None or self.credchange_recommended_days == -1:
+ 989            return False
+ 990        now_date = datetime.now(timezone.utc)
+ 991        return (now_date - change_date).days > self.credchange_recommended_days
+ 992
+ 993    # ---------- Datetime Functions ----------
+ 994
+ 995    def _encode_time(self, value):
+ 996        """`bytes` or `str`: Convert datetime to base64 or plaintext string"""
+ 997
+ 998        if self.version >= (4, 0):
+ 999            diff_seconds = int(
+1000                (
+1001                    value -
+1002                    datetime(
+1003                        year=1,
+1004                        month=1,
+1005                        day=1,
+1006                        tzinfo=timezone.utc
+1007                    )
+1008                ).total_seconds()
+1009            )
+1010            return base64.b64encode(
+1011                struct.pack('<Q', diff_seconds)
+1012            ).decode('utf-8')
+1013        else:
+1014            return value.isoformat()
+1015
+1016    def _decode_time(self, text):
+1017        """`datetime.datetime`: Convert base64 time or plaintext time to datetime"""
+1018
+1019        if self.version >= (4, 0):
+1020            # decode KDBX4 date from b64 format
+1021            try:
+1022                return (
+1023                    datetime(year=1, month=1, day=1, tzinfo=timezone.utc) +
+1024                    timedelta(
+1025                        seconds=struct.unpack('<Q', base64.b64decode(text))[0]
+1026                    )
+1027                )
+1028            except BinasciiError:
+1029                return datetime.fromisoformat(text.replace('Z','+00:00')).replace(tzinfo=timezone.utc)
+1030        else:
+1031            return datetime.fromisoformat(text.replace('Z','+00:00')).replace(tzinfo=timezone.utc)
+
+ + +

Open a KeePass database

+ +
Arguments:
+ +
    +
  • filename (str, optional): path to database or stream object. +If None, the path given when the database was opened is used.
  • +
  • password (str, optional): database password. If None, +database is assumed to have no password
  • +
  • keyfile (str, optional): path to keyfile. If None, +database is assumed to have no keyfile
  • +
  • transformed_key (bytes, optional): precomputed transformed +key.
  • +
  • decrypt (bool, optional): whether to decrypt XML payload. +Set False to access outer header information without decrypting +database.
  • +
+ +
Raises:
+ +
    +
  • CredentialsError: raised when password/keyfile or transformed key +are wrong
  • +
  • HeaderChecksumError: raised when checksum in database header is +is wrong. e.g. database tampering or file corruption
  • +
  • PayloadChecksumError: raised when payload blocks checksum is wrong, +e.g. corruption during database saving
  • +
+
+ + +
+ +
+ + PyKeePass( filename, password=None, keyfile=None, transformed_key=None, decrypt=True) + + + +
+ +
66    def __init__(self, filename, password=None, keyfile=None,
+67                 transformed_key=None, decrypt=True):
+68
+69        self.read(
+70            filename=filename,
+71            password=password,
+72            keyfile=keyfile,
+73            transformed_key=transformed_key,
+74            decrypt=decrypt
+75        )
+
+ + + + +
+
+ +
+ + def + read( self, filename=None, password=None, keyfile=None, transformed_key=None, decrypt=True): + + + +
+ +
 84    def read(self, filename=None, password=None, keyfile=None,
+ 85             transformed_key=None, decrypt=True):
+ 86        """
+ 87        See class docstring.
+ 88        """
+ 89
+ 90        # TODO: - raise, no filename provided, database not open
+ 91        self._password = password
+ 92        self._keyfile = keyfile
+ 93        if filename:
+ 94            self.filename = filename
+ 95        else:
+ 96            filename = self.filename
+ 97
+ 98        try:
+ 99            if hasattr(filename, "read"):
+100                self.kdbx = KDBX.parse_stream(
+101                    filename,
+102                    password=password,
+103                    keyfile=keyfile,
+104                    transformed_key=transformed_key,
+105                    decrypt=decrypt
+106                )
+107            else:
+108                self.kdbx = KDBX.parse_file(
+109                    filename,
+110                    password=password,
+111                    keyfile=keyfile,
+112                    transformed_key=transformed_key,
+113                    decrypt=decrypt
+114                )
+115
+116        except CheckError as e:
+117            if e.path == '(parsing) -> header -> sig_check':
+118                raise HeaderChecksumError("Not a KeePass database")
+119            else:
+120                raise
+121
+122        # body integrity/verification
+123        except ChecksumError as e:
+124            if e.path in (
+125                    '(parsing) -> body -> cred_check', # KDBX4
+126                    '(parsing) -> cred_check' # KDBX3
+127                    ):
+128                raise CredentialsError("Invalid credentials")
+129            elif e.path == '(parsing) -> body -> sha256':
+130                raise HeaderChecksumError("Corrupted database")
+131            elif e.path in (
+132                    '(parsing) -> body -> payload -> hmac_hash', # KDBX4
+133                    '(parsing) -> xml -> block_hash' # KDBX3
+134                    ):
+135                raise PayloadChecksumError("Error reading database contents")
+136            else:
+137                raise
+
+ + +

See class docstring.

+
+ + +
+
+ +
+ + def + reload(self): + + + +
+ +
139    def reload(self):
+140        """Reload current database using previously given credentials """
+141
+142        self.read(self.filename, self.password, self.keyfile)
+
+ + +

Reload current database using previously given credentials

+
+ + +
+
+ +
+ + def + save(self, filename=None, transformed_key=None): + + + +
+ +
144    def save(self, filename=None, transformed_key=None):
+145        """Save current database object to disk.
+146
+147        Args:
+148            filename (`str`, optional): path to database or stream object.
+149                If None, the path given when the database was opened is used.
+150                PyKeePass.filename is unchanged.
+151            transformed_key (`bytes`, optional): precomputed transformed
+152                key.
+153        """
+154
+155        if not filename:
+156            filename = self.filename
+157
+158        if hasattr(filename, "write"):
+159            KDBX.build_stream(
+160                self.kdbx,
+161                filename,
+162                password=self.password,
+163                keyfile=self.keyfile,
+164                transformed_key=transformed_key,
+165                decrypt=True
+166            )
+167        else:
+168            # save to temporary file to prevent database clobbering
+169            # see issues 223, 101
+170            filename_tmp = Path(filename).with_suffix('.tmp')
+171            try:
+172                KDBX.build_file(
+173                    self.kdbx,
+174                    filename_tmp,
+175                    password=self.password,
+176                    keyfile=self.keyfile,
+177                    transformed_key=transformed_key,
+178                    decrypt=True
+179                )
+180            except Exception as e:
+181                os.remove(filename_tmp)
+182                raise e
+183            shutil.move(filename_tmp, filename)
+
+ + +

Save current database object to disk.

+ +
Arguments:
+ +
    +
  • filename (str, optional): path to database or stream object. +If None, the path given when the database was opened is used. +PyKeePass.filename is unchanged.
  • +
  • transformed_key (bytes, optional): precomputed transformed +key.
  • +
+
+ + +
+
+ +
+ version + + + +
+ +
185    @property
+186    def version(self):
+187        """`tuple` of `int`: Length 2 tuple of ints containing major and minor versions.
+188        Generally (3, 1) or (4, 0)."""
+189        return (
+190            self.kdbx.header.value.major_version,
+191            self.kdbx.header.value.minor_version
+192        )
+
+ + +

tuple of int: Length 2 tuple of ints containing major and minor versions. +Generally (3, 1) or (4, 0).

+
+ + +
+
+ +
+ encryption_algorithm + + + +
+ +
194    @property
+195    def encryption_algorithm(self):
+196        """`str`: encryption algorithm used by database during decryption.
+197        Can be one of 'aes256', 'chacha20', or 'twofish'."""
+198        return self.kdbx.header.value.dynamic_header.cipher_id.data
+
+ + +

str: encryption algorithm used by database during decryption. +Can be one of 'aes256', 'chacha20', or 'twofish'.

+
+ + +
+
+ +
+ kdf_algorithm + + + +
+ +
200    @property
+201    def kdf_algorithm(self):
+202        """`str`: key derivation algorithm used by database during decryption.
+203        Can be one of 'aeskdf', 'argon2', or 'aeskdf'"""
+204        if self.version == (3, 1):
+205            return 'aeskdf'
+206        elif self.version == (4, 0):
+207            kdf_parameters = self.kdbx.header.value.dynamic_header.kdf_parameters.data.dict
+208            if kdf_parameters['$UUID'].value == kdf_uuids['argon2']:
+209                return 'argon2'
+210            elif kdf_parameters['$UUID'].value == kdf_uuids['argon2id']:
+211                return 'argon2id'
+212            elif kdf_parameters['$UUID'].value == kdf_uuids['aeskdf']:
+213                return 'aeskdf'
+
+ + +

str: key derivation algorithm used by database during decryption. +Can be one of 'aeskdf', 'argon2', or 'aeskdf'

+
+ + +
+
+ +
+ transformed_key + + + +
+ +
215    @property
+216    def transformed_key(self):
+217        """`bytes`: transformed key used in database decryption.  May be cached
+218        and passed to `open` for faster database opening"""
+219        return self.kdbx.body.transformed_key
+
+ + +

bytes: transformed key used in database decryption. May be cached +and passed to open for faster database opening

+
+ + +
+
+ +
+ database_salt + + + +
+ +
221    @property
+222    def database_salt(self):
+223       """`bytes`: salt of database kdf. This can be used for adding additional
+224       credentials which are used in extension to current keyfile."""
+225
+226       if self.version == (3, 1):
+227            return self.kdbx.header.value.dynamic_header.transform_seed.data
+228
+229       kdf_parameters = self.kdbx.header.value.dynamic_header.kdf_parameters.data.dict
+230       return kdf_parameters['S'].value
+
+ + +

bytes: salt of database kdf. This can be used for adding additional +credentials which are used in extension to current keyfile.

+
+ + +
+
+ +
+ payload + + + +
+ +
232    @property
+233    def payload(self):
+234        """`construct.Container`: Encrypted payload of keepass database"""
+235
+236        # check if payload is decrypted
+237        if self.kdbx.body.payload is None:
+238            raise ValueError("Database is not decrypted")
+239        else:
+240            return self.kdbx.body.payload
+
+ + +

construct.Container: Encrypted payload of keepass database

+
+ + +
+
+ +
+ tree + + + +
+ +
242    @property
+243    def tree(self):
+244        """`lxml.etree._ElementTree`: database XML payload"""
+245        return self.payload.xml
+
+ + +

lxml.etree._ElementTree: database XML payload

+
+ + +
+
+ +
+ root_group + + + +
+ +
247    @property
+248    def root_group(self):
+249        """`Group`: root Group of database"""
+250        return self.find_groups(path='', first=True)
+
+ + +

Group: root Group of database

+
+ + +
+
+ +
+ recyclebin_group + + + +
+ +
252    @property
+253    def recyclebin_group(self):
+254        """`Group`: RecycleBin Group of database"""
+255        elem = self._xpath('/KeePassFile/Meta/RecycleBinUUID', first=True)
+256        recyclebin_uuid = uuid.UUID( bytes = base64.b64decode(elem.text) )
+257        return self.find_groups(uuid=recyclebin_uuid, first=True)
+
+ + +

Group: RecycleBin Group of database

+
+ + +
+
+ +
+ groups + + + +
+ +
259    @property
+260    def groups(self):
+261        """`list` of `Group`: all groups in database
+262        """
+263        return self.find_groups()
+
+ + +

list of Group: all groups in database

+
+ + +
+
+ +
+ entries + + + +
+ +
265    @property
+266    def entries(self):
+267        """`list` of `Entry`: all entries in database,
+268        excluding history"""
+269        return self.find_entries()
+
+ + +

list of Entry: all entries in database, +excluding history

+
+ + +
+
+ +
+ database_name + + + +
+ +
271    @property
+272    def database_name(self):
+273        """`str`: Name of database"""
+274        elem = self._xpath('/KeePassFile/Meta/DatabaseName', first=True)
+275        return elem.text
+
+ + +

str: Name of database

+
+ + +
+
+ +
+ database_description + + + +
+ +
282    @property
+283    def database_description(self):
+284        """`str`: Description of the database"""
+285        elem = self._xpath('/KeePassFile/Meta/DatabaseDescription', first=True)
+286        return elem.text
+
+ + +

str: Description of the database

+
+ + +
+
+ +
+ default_username + + + +
+ +
293    @property
+294    def default_username(self):
+295        """`str` or `None`: default user.  `None` if not set"""
+296        elem = self._xpath('/KeePassFile/Meta/DefaultUserName', first=True)
+297        return elem.text
+
+ + +

str or None: default user. None if not set

+
+ + +
+
+ +
+ + def + xml(self): + + + +
+ +
304    def xml(self):
+305        """Get XML part of database as string
+306
+307        Returns:
+308            `str`: XML content of database
+309        """
+310        return etree.tostring(
+311            self.tree,
+312            pretty_print=True,
+313            standalone=True,
+314            encoding='utf-8'
+315        )
+
+ + +

Get XML part of database as string

+ +
Returns:
+ +
+

str: XML content of database

+
+
+ + +
+
+ +
+ + def + dump_xml(self, filename): + + + +
+ +
317    def dump_xml(self, filename):
+318        """ Dump the contents of the database to file as XML
+319
+320        Args:
+321            filename (`str`): path to output file
+322        """
+323        with open(filename, 'wb') as f:
+324            f.write(self.xml())
+
+ + +

Dump the contents of the database to file as XML

+ +
Arguments:
+ +
    +
  • filename (str): path to output file
  • +
+
+ + +
+
+ +
+ + def + xpath(self, xpath_str, tree=None, first=False, cast=False, **kwargs): + + + +
+ +
326    def xpath(self, xpath_str, tree=None, first=False, cast=False, **kwargs):
+327        """Look up elements in the XML payload and return corresponding object.
+328
+329        Internal function which searches the payload lxml ElementTree for
+330        elements via XPath.  Matched entry, group, and attachment elements are
+331        automatically cast to their corresponding objects, otherwise an error
+332        is raised.
+333
+334        Args:
+335            xpath_str (`str`): XPath query for finding element(s)
+336            tree (`_ElementTree`, `Element`, optional): use this
+337                element as root node when searching
+338            first (`bool`): If True, function returns first result or None.  If
+339                False, function returns list of matches or empty list.
+340                    (default `False`).
+341            cast (`bool`): If True, matches are instead instantiated as
+342                pykeepass Group, Entry, or Attachment objects.  An exception
+343                is raised if a match cannot be cast.  (default `False`)
+344
+345        Returns:
+346            `list` of `Group`, `Entry`, `Attachment`, or `lxml.etree.Element`
+347        """
+348
+349        if tree is None:
+350            tree = self.tree
+351        logger.debug('xpath query: ' + xpath_str)
+352        elements = tree.xpath(
+353            xpath_str, namespaces={'re': 'http://exslt.org/regular-expressions'}
+354        )
+355
+356        res = []
+357        for e in elements:
+358            if cast:
+359                if e.tag == 'Entry':
+360                    res.append(Entry(element=e, kp=self))
+361                elif e.tag == 'Group':
+362                    res.append(Group(element=e, kp=self))
+363                elif e.tag == 'Binary' and e.getparent().tag == 'Entry':
+364                    res.append(Attachment(element=e, kp=self))
+365                else:
+366                    raise Exception('Could not cast element {}'.format(e))
+367            else:
+368                res.append(e)
+369
+370        # return first object in list or None
+371        if first:
+372            res = res[0] if res else None
+373
+374        return res
+
+ + +

Look up elements in the XML payload and return corresponding object.

+ +

Internal function which searches the payload lxml ElementTree for +elements via XPath. Matched entry, group, and attachment elements are +automatically cast to their corresponding objects, otherwise an error +is raised.

+ +
Arguments:
+ +
    +
  • xpath_str (str): XPath query for finding element(s)
  • +
  • tree (_ElementTree, Element, optional): use this +element as root node when searching
  • +
  • first (bool): If True, function returns first result or None. If +False, function returns list of matches or empty list. + (default False).
  • +
  • cast (bool): If True, matches are instead instantiated as +pykeepass Group, Entry, or Attachment objects. An exception +is raised if a match cannot be cast. (default False)
  • +
+ +
Returns:
+ +
+

list of Group, Entry, Attachment, or lxml.etree.Element

+
+
+ + +
+
+ +
+ + def + find_groups(self, recursive=True, path=None, group=None, **kwargs): + + + +
+ +
462    def find_groups(self, recursive=True, path=None, group=None, **kwargs):
+463        """ Find groups in a database
+464
+465        [XSLT style]: https://www.xml.com/pub/a/2003/06/04/tr.html
+466        [flags]: https://www.w3.org/TR/xpath-functions/#flags
+467
+468        Args:
+469            name (`str`): name of group
+470            first (`bool`): return first result instead of list (default `False`)
+471            recursive (`bool`): do a recursive search of all groups/subgroups
+472            path (`list` of `str`): do group search starting from path
+473            group (`Group`): search underneath group
+474            uuid (`uuid.UUID`): group UUID
+475            regex (`bool`): whether `str` search arguments contain [XSLT style][XSLT style] regular expression
+476            flags (`str`): XPath [flags][flags]
+477
+478        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.
+479
+480        The `group` argument determines what `Group` to search under, and the `recursive` boolean controls whether to search recursively.
+481
+482        The `first` (default `False`) boolean controls whether to return the first matched item, or a list of matched items.
+483
+484        - if `first=False`, the function returns a list of `Group` or `[]` if there are no matches
+485        - if `first=True`, the function returns the first `Group` match, or `None` if there are no matches
+486
+487        Returns:
+488            `list` of `Group` if `first=False`
+489            or (`Group` or `None`) if `first=True`
+490
+491        Examples:
+492        ``` python
+493        >>> kp.find_groups(name='foo', first=True)
+494        Group: "foo"
+495
+496        >>> kp.find_groups(name='foo.*', regex=True)
+497        [Group: "foo", Group "foobar"]
+498
+499        >>> kp.find_groups(path=['social'], regex=True)
+500        [Group: "social", Group: "social/foo_subgroup"]
+501
+502        >>> kp.find_groups(name='social', first=True).subgroups
+503        [Group: "social/foo_subgroup"]
+504        ```
+505        """
+506
+507        prefix = '//Group' if recursive else '/Group'
+508        res = self._find(prefix, group_xp, path=path, tree=group, **kwargs)
+509        return res
+
+ + +

Find groups in a database

+ +
Arguments:
+ +
    +
  • name (str): name of group
  • +
  • first (bool): return first result instead of list (default False)
  • +
  • recursive (bool): do a recursive search of all groups/subgroups
  • +
  • path (list of str): do group search starting from path
  • +
  • group (Group): search underneath group
  • +
  • uuid (uuid.UUID): group UUID
  • +
  • regex (bool): whether str search arguments contain XSLT style regular expression
  • +
  • flags (str): XPath 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 or [] if there are no matches
  • +
  • if first=True, the function returns the first Group match, or None if there are no matches
  • +
+ +
Returns:
+ +
+

list of Group if first=False + or (Group or None) if first=True

+
+ +

Examples:

+ +
+
>>> 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"]
+
+
+
+ + +
+
+ +
+ + def + add_group(self, destination_group, group_name, icon=None, notes=None): + + + +
+ +
511    def add_group(self, destination_group, group_name, icon=None, notes=None):
+512        """Create a new group and all parent groups, if necessary
+513
+514        Args:
+515            destination_group (`Group`): parent group to add a new group to
+516            group_name (`str`): name of new group
+517            icon (`str`): icon name from `icons`
+518            notes (`str`): group notes
+519
+520        Returns:
+521            `Group`: newly added group
+522        """
+523        logger.debug('Creating group {}'.format(group_name))
+524
+525        if icon:
+526            group = Group(name=group_name, icon=icon, notes=notes, kp=self)
+527        else:
+528            group = Group(name=group_name, notes=notes, kp=self)
+529        destination_group.append(group)
+530
+531        return group
+
+ + +

Create a new group and all parent groups, if necessary

+ +
Arguments:
+ +
    +
  • destination_group (Group): parent group to add a new group to
  • +
  • group_name (str): name of new group
  • +
  • icon (str): icon name from icons
  • +
  • notes (str): group notes
  • +
+ +
Returns:
+ +
+

Group: newly added group

+
+
+ + +
+
+ +
+ + def + delete_group(self, group): + + + +
+ +
533    def delete_group(self, group):
+534        group.delete()
+
+ + + + +
+
+ +
+ + def + move_group(self, group, destination_group): + + + +
+ +
536    def move_group(self, group, destination_group):
+537        """Move a group"""
+538        destination_group.append(group)
+
+ + +

Move a group

+
+ + +
+
+ +
+ + def + trash_group(self, group): + + + +
+ +
550    def trash_group(self, group):
+551        """Move a group to the RecycleBin
+552
+553        The recycle bin is created if it does not exit. ``group`` must be an empty Group.
+554
+555        Args:
+556            group (`Group`): Group to send to the RecycleBin
+557        """
+558        if not self._can_be_moved_to_recyclebin(group):
+559            raise UnableToSendToRecycleBin
+560        recyclebin_group = self._create_or_get_recyclebin_group()
+561        self.move_group( group, recyclebin_group)
+
+ + +

Move a group to the RecycleBin

+ +

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

+ +
Arguments:
+ +
    +
  • group (Group): Group to send to the RecycleBin
  • +
+
+ + +
+
+ +
+ + def + empty_group(self, group): + + + +
+ +
563    def empty_group(self, group):
+564        """Delete all entries and subgroups of a group.
+565
+566        This does not delete the group itself
+567
+568        Args:
+569            group (`Group`): Group to empty
+570        """
+571        while len(group.subgroups):
+572            self.delete_group(group.subgroups[0])
+573        while len(group.entries):
+574            self.delete_entry(group.entries[0])
+
+ + +

Delete all entries and subgroups of a group.

+ +

This does not delete the group itself

+ +
Arguments:
+ +
    +
  • group (Group): Group to empty
  • +
+
+ + +
+
+ +
+ + def + find_entries(self, recursive=True, path=None, group=None, **kwargs): + + + +
+ +
590    def find_entries(self, recursive=True, path=None, group=None, **kwargs):
+591        """Returns entries which match all provided parameters
+592        Args:
+593            path (`list` of (`str` or `None`), optional): full path to an entry
+594                (eg. `['foobar_group', 'foobar_entry']`).  This implies `first=True`.
+595                All other arguments are ignored when this is given.  This is useful for
+596                handling user input.
+597            title (`str`, optional): title of entry to find
+598            username (`str`, optional): username of entry to find
+599            password (`str`, optional): password of entry to find
+600            url (`str`, optional): url of entry to find
+601            notes (`str`, optional): notes of entry to find
+602            otp (`str`, optional): otp string of entry to find
+603            string (`dict`): custom string fields.
+604                (eg. `{'custom_field1': 'custom value', 'custom_field2': 'custom value'}`)
+605            uuid (`uuid.UUID`): entry UUID
+606            tags (`list` of `str`): entry tags
+607            autotype_enabled (`bool`, optional): autotype string is enabled
+608            autotype_sequence (`str`, optional): autotype string
+609            autotype_window (`str`, optional): autotype target window filter string
+610            group (`Group` or `None`, optional): search under this group
+611            first (`bool`, optional): return first match or `None` if no matches.
+612                Otherwise return list of `Entry` matches. (default `False`)
+613            history (`bool`): include history entries in results. (default `False`)
+614            recursive (`bool`): search recursively
+615            regex (`bool`): interpret search strings given above as
+616                [XSLT style](https://www.xml.com/pub/a/2003/06/04/tr.html) regexes
+617            flags (`str`): regex [search flags](https://www.w3.org/TR/xpath-functions/#flags)
+618
+619        Returns:
+620            `list` of `Entry` if `first=False`
+621            or (`Entry` or `None`) if `first=True`
+622
+623        Examples:
+624
+625        ``` python
+626        >>> kp.find_entries(title='gmail', first=True)
+627        Entry: "social/gmail (myusername)"
+628
+629        >>> kp.find_entries(title='foo.*', regex=True)
+630        [Entry: "foo_entry (myusername)", Entry: "foobar_entry (myusername)"]
+631
+632        >>> entry = kp.find_entries(title='foo.*', url='.*facebook.*', regex=True, first=True)
+633        >>> entry.url
+634        'facebook.com'
+635        >>> entry.title
+636        'foo_entry'
+637        >>> entry.title = 'hello'
+638
+639        >>> group = kp.find_group(name='social', first=True)
+640        >>> kp.find_entries(title='facebook', group=group, recursive=False, first=True)
+641        Entry: "social/facebook (myusername)"
+642        ```
+643        """
+644
+645        prefix = '//Entry' if recursive else '/Entry'
+646        res = self._find(prefix, entry_xp, path=path, tree=group, **kwargs)
+647
+648        return res
+
+ + +

Returns entries which match all provided parameters

+ +
Arguments:
+ +
    +
  • path (list of (str or None), optional): full path to an entry +(eg. ['foobar_group', 'foobar_entry']). This implies first=True. +All other arguments are ignored when this is given. This is useful for +handling user input.
  • +
  • title (str, optional): title of entry to find
  • +
  • username (str, optional): username of entry to find
  • +
  • password (str, optional): password of entry to find
  • +
  • url (str, optional): url of entry to find
  • +
  • notes (str, optional): notes of entry to find
  • +
  • otp (str, optional): otp string of entry to find
  • +
  • string (dict): custom string fields. +(eg. {'custom_field1': 'custom value', 'custom_field2': 'custom value'})
  • +
  • uuid (uuid.UUID): entry UUID
  • +
  • tags (list of str): entry tags
  • +
  • autotype_enabled (bool, optional): autotype string is enabled
  • +
  • autotype_sequence (str, optional): autotype string
  • +
  • autotype_window (str, optional): autotype target window filter string
  • +
  • group (Group or None, optional): search under this group
  • +
  • first (bool, optional): return first match or None if no matches. +Otherwise return list of Entry matches. (default False)
  • +
  • history (bool): include history entries in results. (default False)
  • +
  • recursive (bool): search recursively
  • +
  • regex (bool): interpret search strings given above as +XSLT style regexes
  • +
  • flags (str): regex search flags
  • +
+ +
Returns:
+ +
+

list of Entry if first=False + or (Entry or None) if first=True

+
+ +

Examples:

+ +
+
>>> 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)"
+
+
+
+ + +
+
+ +
+ + def + add_entry( self, destination_group, title, username, password, url=None, notes=None, expiry_time=None, tags=None, otp=None, icon=None, force_creation=False): + + + +
+ +
651    def add_entry(self, destination_group, title, username,
+652                  password, url=None, notes=None, expiry_time=None,
+653                  tags=None, otp=None, icon=None, force_creation=False):
+654
+655        """Create a new entry
+656
+657        Args:
+658            destination_group (`Group`): parent group to add a new entry to
+659            title (`str`, or `None`): title of new entry
+660            username (`str` or `None`): username of new entry
+661            password (`str` or `None`): password of new entry
+662            url (`str` or `None`): URL of new entry
+663            notes (`str` or `None`): notes of new entry
+664            expiry_time (`datetime.datetime`): time of entry expiration
+665            tags (`list` of `str` or `None`): entry tags
+666            otp (`str` or `None`): OTP code of object
+667            icon (`str`, optional): icon name from `icons`
+668            force_creation (`bool`): create entry even if one with identical
+669                title exists in this group (default `False`)
+670
+671        If ``expiry_time`` is a naive datetime object
+672        (i.e. ``expiry_time.tzinfo`` is not set), the timezone is retrieved from
+673        ``dateutil.tz.gettz()``.
+674
+675
+676        Returns:
+677            `Group`: newly added group
+678        """
+679
+680        entries = self.find_entries(
+681            title=title,
+682            username=username,
+683            first=True,
+684            group=destination_group,
+685            recursive=False
+686        )
+687
+688        if entries and not force_creation:
+689            raise Exception(
+690                'An entry "{}" already exists in "{}"'.format(
+691                    title, destination_group
+692                )
+693            )
+694        else:
+695            logger.debug('Creating a new entry')
+696            entry = Entry(
+697                title=title,
+698                username=username,
+699                password=password,
+700                notes=notes,
+701                otp=otp,
+702                url=url,
+703                tags=tags,
+704                expires=True if expiry_time else False,
+705                expiry_time=expiry_time,
+706                icon=icon,
+707                kp=self
+708            )
+709            destination_group.append(entry)
+710
+711        return entry
+
+ + +

Create a new entry

+ +
Arguments:
+ +
    +
  • destination_group (Group): parent group to add a new entry to
  • +
  • title (str, or None): title of new entry
  • +
  • username (str or None): username of new entry
  • +
  • password (str or None): password of new entry
  • +
  • url (str or None): URL of new entry
  • +
  • notes (str or None): notes of new entry
  • +
  • expiry_time (datetime.datetime): time of entry expiration
  • +
  • tags (list of str or None): entry tags
  • +
  • otp (str or None): OTP code of object
  • +
  • icon (str, optional): icon name from icons
  • +
  • force_creation (bool): create entry even if one with identical +title exists in this group (default False)
  • +
+ +

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

+ +
Returns:
+ +
+

Group: newly added group

+
+
+ + +
+
+ +
+ + def + delete_entry(self, entry): + + + +
+ +
713    def delete_entry(self, entry):
+714        """Delete entry
+715
+716        Args:
+717            entry (`Entry`): entry to delete
+718        """
+719        entry.delete()
+
+ + +

Delete entry

+ +
Arguments:
+ +
    +
  • entry (Entry): entry to delete
  • +
+
+ + +
+
+ +
+ + def + move_entry(self, entry, destination_group): + + + +
+ +
721    def move_entry(self, entry, destination_group):
+722        """Move entry to group
+723
+724        Args:
+725            entry (`Entry`): entry to move
+726            destination_group (`Group`): group to move to
+727        """
+728        destination_group.append(entry)
+
+ + +

Move entry to group

+ +
Arguments:
+ +
    +
  • entry (Entry): entry to move
  • +
  • destination_group (Group): group to move to
  • +
+
+ + +
+
+ +
+ + def + trash_entry(self, entry): + + + +
+ +
730    def trash_entry(self, entry):
+731        """Move an entry to the RecycleBin
+732
+733        The recycle bin is created if it does not exit.
+734
+735        Args:
+736            entry (`Entry`): Entry to send to the RecycleBin
+737        """
+738        if not self._can_be_moved_to_recyclebin(entry):
+739            raise UnableToSendToRecycleBin
+740        recyclebin_group = self._create_or_get_recyclebin_group()
+741        self.move_entry( entry, recyclebin_group)
+
+ + +

Move an entry to the RecycleBin

+ +

The recycle bin is created if it does not exit.

+ +
Arguments:
+ +
    +
  • entry (Entry): Entry to send to the RecycleBin
  • +
+
+ + +
+
+ +
+ + def + find_attachments(self, recursive=True, path=None, element=None, **kwargs): + + + +
+ +
745    def find_attachments(self, recursive=True, path=None, element=None, **kwargs):
+746        """ Find attachments in database
+747
+748        Args:
+749            id (`int` or `None`): attachment ID to match
+750            filename (`str` or `None`): filename to match
+751            element (`Entry` or `Group` or `None`): entry or group to search under
+752            recursive (`bool`): search recursively (default `True`)
+753            regex (`bool`): whether `str` search arguments contain [XSLT style][XSLT style] regular expression
+754            flags (`str`): XPath [flags][flags]
+755            history (`bool`): search under history entries. (default `False`)
+756            first (`bool`): If True, function returns first result or None.  If
+757                False, function returns list of matches or empty list.  (default
+758                `False`).
+759        """
+760
+761        prefix = '//Binary' if recursive else '/Binary'
+762        res = self._find(prefix, attachment_xp, path=path, tree=element, **kwargs)
+763
+764        return res
+
+ + +

Find attachments in database

+ +
Arguments:
+ +
    +
  • id (int or None): attachment ID to match
  • +
  • filename (str or None): filename to match
  • +
  • element (Entry or Group or None): entry or group to search under
  • +
  • recursive (bool): search recursively (default True)
  • +
  • regex (bool): whether str search arguments contain [XSLT style][XSLT style] regular expression
  • +
  • flags (str): XPath [flags][flags]
  • +
  • history (bool): search under history entries. (default False)
  • +
  • first (bool): If True, function returns first result or None. If +False, function returns list of matches or empty list. (default +False).
  • +
+
+ + +
+
+ +
+ attachments + + + +
+ +
766    @property
+767    def attachments(self):
+768        """`list` of `Attachment`: all attachments in database"""
+769        return self.find_attachments(filename='.*', regex=True)
+
+ + +

list of Attachment: all attachments in database

+
+ + +
+
+ +
+ binaries + + + +
+ +
771    @property
+772    def binaries(self):
+773        """`list` of `bytes`: all attachment binaries in database.  The position
+774        within this list indicates the binary's ID"""
+775        if self.version >= (4, 0):
+776            # first byte is a prepended flag
+777            binaries = [a.data[1:] for a in self.payload.inner_header.binary]
+778        else:
+779            binaries = []
+780            for elem in self._xpath('/KeePassFile/Meta/Binaries/Binary'):
+781                if elem.text is not None:
+782                    if elem.get('Compressed') == 'True':
+783                        data = zlib.decompress(
+784                            base64.b64decode(elem.text),
+785                            zlib.MAX_WBITS | 32
+786                        )
+787                    else:
+788                        data = base64.b64decode(elem.text)
+789                else:
+790                    data = b''
+791                binaries.insert(int(elem.attrib['ID']), data)
+792
+793        return binaries
+
+ + +

list of bytes: all attachment binaries in database. The position +within this list indicates the binary's ID

+
+ + +
+
+ +
+ + def + add_binary(self, data, compressed=True, protected=True): + + + +
+ +
795    def add_binary(self, data, compressed=True, protected=True):
+796        """Add binary data to database.  Note this does not create an attachment (see `Entry.add_attachment`)
+797
+798        Args:
+799            data (`bytes`): binary data
+800            compressed (`bool`): whether binary data should be compressed.
+801                (default `True`).  Applies only to KDBX3
+802            protected (`bool`): whether protected flag should be set.  (default `True`).  Note
+803                Applies only to KDBX4
+804
+805        Returns:
+806            id (`int`): ID of binary in database
+807        """
+808        if self.version >= (4, 0):
+809            # add protected flag byte
+810            data = b'\x01' + data if protected else b'\x00' + data
+811            # add binary element to inner header
+812            c = Container(type='binary', data=data)
+813            self.payload.inner_header.binary.append(c)
+814        else:
+815            binaries = self._xpath(
+816                '/KeePassFile/Meta/Binaries',
+817                first=True
+818            )
+819            if compressed:
+820                # gzip compression
+821                compressor = zlib.compressobj(
+822                    zlib.Z_DEFAULT_COMPRESSION,
+823                    zlib.DEFLATED,
+824                    zlib.MAX_WBITS | 16
+825                )
+826                data = compressor.compress(data)
+827                data += compressor.flush()
+828            data = base64.b64encode(data).decode()
+829
+830            # set ID for Binary Element
+831            binary_id = len(self.binaries)
+832
+833            # add binary element to XML
+834            binaries.append(
+835                E.Binary(data, ID=str(binary_id), Compressed=str(compressed))
+836            )
+837
+838        # return binary id
+839        return len(self.binaries) - 1
+
+ + +

Add binary data to database. Note this does not create an attachment (see Entry.add_attachment)

+ +
Arguments:
+ +
    +
  • data (bytes): binary data
  • +
  • compressed (bool): whether binary data should be compressed. +(default True). Applies only to KDBX3
  • +
  • protected (bool): whether protected flag should be set. (default True). Note +Applies only to KDBX4
  • +
+ +
Returns:
+ +
+

id (int): ID of binary in database

+
+
+ + +
+
+ +
+ + def + delete_binary(self, id): + + + +
+ +
841    def delete_binary(self, id):
+842        """Remove a binary from database and deletes attachments that reference it
+843
+844        Since attachments reference binaries by their positional index,
+845        attachments that reference binaries with ID > `id` will automatically be decremented
+846
+847        Args:
+848            id (`int`): ID of binary to remove
+849
+850        Raises:
+851            `IndexError`: raised when binary with given ID does not exist
+852        """
+853        try:
+854            if self.version >= (4, 0):
+855                # remove binary element from inner header
+856                self.payload.inner_header.binary.pop(id)
+857            else:
+858                # remove binary element from XML
+859                binaries = self._xpath('/KeePassFile/Meta/Binaries', first=True)
+860                binaries.remove(binaries.getchildren()[id])
+861        except IndexError:
+862            raise BinaryError('No such binary with id {}'.format(id))
+863
+864        # remove all entry references to this attachment
+865        for reference in self.find_attachments(id=id):
+866            reference.delete()
+867
+868        # decrement references greater than this id
+869        binaries_gt = self._xpath(
+870            '//Binary/Value[@Ref > "{}"]/..'.format(id),
+871            cast=True
+872        )
+873        for reference in binaries_gt:
+874            reference.id = reference.id - 1
+
+ + +

Remove a binary from database and deletes attachments that reference it

+ +

Since attachments reference binaries by their positional index, +attachments that reference binaries with ID > id will automatically be decremented

+ +
Arguments:
+ +
    +
  • id (int): ID of binary to remove
  • +
+ +
Raises:
+ +
    +
  • IndexError: raised when binary with given ID does not exist
  • +
+
+ + +
+
+ +
+ + def + deref(self, value): + + + +
+ +
878    def deref(self, value):
+879
+880        """Dereference [field reference][fieldref] of Entry
+881
+882        Args:
+883            ref (`str`): KeePass reference string to another field
+884
+885        Returns:
+886            `str`, `uuid.UUID` or `None` if no match found
+887
+888        [fieldref]: https://keepass.info/help/base/fieldrefs.html
+889        """
+890        if not value:
+891            return value
+892        references = set(re.findall(r'({REF:([TUPANI])@([TUPANI]):([^}]+)})', value))
+893        if not references:
+894            return value
+895        field_to_attribute = {
+896            'T': 'title',
+897            'U': 'username',
+898            'P': 'password',
+899            'A': 'url',
+900            'N': 'notes',
+901            'I': 'uuid',
+902        }
+903        for ref, wanted_field, search_in, search_value in references:
+904            wanted_field = field_to_attribute[wanted_field]
+905            search_in = field_to_attribute[search_in]
+906            if search_in == 'uuid':
+907                search_value = uuid.UUID(search_value)
+908            ref_entry = self.find_entries(first=True, **{search_in: search_value})
+909            if ref_entry is None:
+910                return None
+911            value = value.replace(ref, getattr(ref_entry, wanted_field))
+912        return self.deref(value)
+
+ + +

Dereference field reference of Entry

+ +
Arguments:
+ +
    +
  • ref (str): KeePass reference string to another field
  • +
+ +
Returns:
+ +
+

str, uuid.UUID or None if no match found

+
+
+ + +
+
+ +
+ password + + + +
+ +
917    @property
+918    def password(self):
+919        """`str`: Get or set database password"""
+920        return self._password
+
+ + +

str: Get or set database password

+
+ + +
+
+ +
+ keyfile + + + +
+ +
927    @property
+928    def keyfile(self):
+929        """`str` or `pathlib.Path`: get or set database keyfile"""
+930        return self._keyfile
+
+ + +

str or pathlib.Path: get or set database keyfile

+
+ + +
+
+ +
+ credchange_required_days + + + +
+ +
937    @property
+938    def credchange_required_days(self):
+939        """`int`: Days until password update should be required"""
+940        e = self._xpath('/KeePassFile/Meta/MasterKeyChangeForce', first=True)
+941        if e is not None:
+942            return int(e.text)
+
+ + +

int: Days until password update should be required

+
+ + +
+ +
+ +
+ credchange_date + + + +
+ +
963    @property
+964    def credchange_date(self):
+965        """`datetime.datetime`: get or set UTC time of last credential change"""
+966        e = self._xpath('/KeePassFile/Meta/MasterKeyChanged', first=True)
+967        if e is not None:
+968            return self._decode_time(e.text)
+
+ + +

datetime.datetime: get or set UTC time of last credential change

+
+ + +
+
+ +
+ credchange_required + + + +
+ +
975    @property
+976    def credchange_required(self):
+977        """`bool`: Check if credential change is required"""
+978        change_date = self.credchange_date
+979        if change_date is None or self.credchange_required_days == -1:
+980            return False
+981        now_date = datetime.now(timezone.utc)
+982        return (now_date - change_date).days > self.credchange_required_days
+
+ + +

bool: Check if credential change is required

+
+ + +
+ +
+
+ +
+ + class + Entry(pykeepass.baseelement.BaseElement): + + + +
+ +
 26class Entry(BaseElement):
+ 27
+ 28    def __init__(self, title=None, username=None, password=None, url=None,
+ 29                 notes=None, otp=None, tags=None, expires=False, expiry_time=None,
+ 30                 icon=None, autotype_sequence=None, autotype_enabled=True, autotype_window=None,
+ 31                 element=None, kp=None):
+ 32
+ 33        self._kp = kp
+ 34
+ 35        if element is None:
+ 36            super().__init__(
+ 37                element=Element('Entry'),
+ 38                kp=kp,
+ 39                expires=expires,
+ 40                expiry_time=expiry_time,
+ 41                icon=icon
+ 42            )
+ 43            self._element.append(E.String(E.Key('Title'), E.Value(title)))
+ 44            self._element.append(E.String(E.Key('UserName'), E.Value(username)))
+ 45            self._element.append(
+ 46                E.String(E.Key('Password'), E.Value(password, Protected="True"))
+ 47            )
+ 48            if url:
+ 49                self._element.append(E.String(E.Key('URL'), E.Value(url)))
+ 50            if notes:
+ 51                self._element.append(E.String(E.Key('Notes'), E.Value(notes)))
+ 52            if otp:
+ 53                self._element.append(
+ 54                    E.String(E.Key('otp'), E.Value(otp, Protected="True"))
+ 55                )
+ 56            if tags:
+ 57                self._element.append(
+ 58                    E.Tags(';'.join(tags) if isinstance(tags, list) else tags)
+ 59                )
+ 60            self._element.append(
+ 61                E.AutoType(
+ 62                    E.Enabled(str(autotype_enabled)),
+ 63                    E.DataTransferObfuscation('0'),
+ 64                    E.DefaultSequence(str(autotype_sequence) if autotype_sequence else ''),
+ 65                    E.Association(
+ 66                        E.Window(str(autotype_window) if autotype_window else ''),
+ 67                        E.KeystrokeSequence('')
+ 68                    )
+ 69                )
+ 70            )
+ 71            # FIXME: include custom_properties in constructor
+ 72
+ 73        else:
+ 74            assert type(element) in [_Element, Element, ObjectifiedElement], \
+ 75                'The provided element is not an LXML Element, but a {}'.format(
+ 76                    type(element)
+ 77                )
+ 78            assert element.tag == 'Entry', 'The provided element is not an Entry '\
+ 79                'element, but a {}'.format(element.tag)
+ 80            self._element = element
+ 81
+ 82    def _get_string_field(self, key):
+ 83        """Get a string field from an entry
+ 84
+ 85        Args:
+ 86            key (`str`): name of field
+ 87
+ 88        Returns:
+ 89            `str` or `None`: field value
+ 90        """
+ 91
+ 92        field = self._xpath('String/Key[text()="{}"]/../Value'.format(key), history=True, first=True)
+ 93        if field is not None:
+ 94            return field.text
+ 95
+ 96    def _set_string_field(self, key, value, protected=None):
+ 97        """Create or overwrite a string field in an Entry
+ 98
+ 99        Args:
+100            key (`str`): name of field
+101            value (`str`): value of field
+102            protected (`bool` or `None`): mark whether the field should be protected in memory
+103                in other tools.  If `None`, value is either copied from existing field or field
+104                is created with protected property unset.
+105
+106        Note: pykeepass does not support memory protection
+107        """
+108        field = self._xpath('String/Key[text()="{}"]/..'.format(key), history=True, first=True)
+109
+110        protected_str = None
+111        if protected is None:
+112            protected_field = self._xpath('String/Key[text()="{}"]/../Value'.format(key), first=True)
+113            if protected_field is not None:
+114                protected_str = protected_field.attrib.get("Protected")
+115        else:
+116            protected_str = str(protected)
+117
+118        if field is not None:
+119            self._element.remove(field)
+120
+121        if protected_str is None:
+122            self._element.append(E.String(E.Key(key), E.Value(value)))
+123        else:
+124            self._element.append(E.String(E.Key(key), E.Value(value, Protected=protected_str)))
+125
+126    def _get_string_field_keys(self, exclude_reserved=False):
+127        results = [x.find('Key').text for x in self._element.findall('String')]
+128        if exclude_reserved:
+129            return [x for x in results if x not in reserved_keys]
+130        else:
+131            return results
+132
+133    @property
+134    def index(self):
+135        """`int`: index of a entry within a group"""
+136        group = self.group._element
+137        children = group.getchildren()
+138        first_index = self.group._first_entry
+139        index = children.index(self._element)
+140        return index - first_index
+141
+142    def reindex(self, new_index):
+143        """Move entry to a new index within a group
+144        
+145        Args:
+146            new_index (`int`): new index for the entry starting at 0
+147        """
+148        group = self.group._element
+149        first_index = self.group._first_entry
+150        group.remove(self._element)
+151        group.insert(new_index+first_index, self._element)
+152
+153    @property
+154    def attachments(self):
+155        """`list` of `Attachment`: attachments associated with entry"""
+156        return self._kp.find_attachments(
+157            element=self,
+158            filename='.*',
+159            regex=True,
+160            recursive=False
+161        )
+162
+163    def add_attachment(self, id, filename):
+164        """Add attachment to entry
+165
+166        The existence of a binary with the given `id` is not checked
+167
+168        Args:
+169            id (`int`): ID of attachment in database
+170            filename (`str`): filename to assign to this attachment data
+171
+172        Returns:
+173            `Attachment`
+174        """
+175        element = E.Binary(
+176            E.Key(filename),
+177            E.Value(Ref=str(id))
+178        )
+179        self._element.append(element)
+180
+181        return attachment.Attachment(element=element, kp=self._kp)
+182
+183    def delete_attachment(self, attachment):
+184        """remove an attachment from entry.  Does not remove binary data"""
+185        attachment.delete()
+186
+187    def deref(self, attribute):
+188        """See `PyKeePass.deref`"""
+189        return self._kp.deref(getattr(self, attribute))
+190
+191    @property
+192    def title(self):
+193        """`str`: get or set entry title"""
+194        return self._get_string_field('Title')
+195
+196    @title.setter
+197    def title(self, value):
+198        return self._set_string_field('Title', value)
+199
+200    @property
+201    def username(self):
+202        """`str`: get or set entry username"""
+203        return self._get_string_field('UserName')
+204
+205    @username.setter
+206    def username(self, value):
+207        return self._set_string_field('UserName', value)
+208
+209    @property
+210    def password(self):
+211        """`str`: get or set entry password"""
+212        return self._get_string_field('Password')
+213
+214    @password.setter
+215    def password(self, value):
+216        if self.password:
+217            return self._set_string_field('Password', value)
+218        else:
+219            return self._set_string_field('Password', value, True)
+220
+221    @property
+222    def url(self):
+223        """str: get or set entry URL"""
+224        return self._get_string_field('URL')
+225
+226    @url.setter
+227    def url(self, value):
+228        return self._set_string_field('URL', value)
+229
+230    @property
+231    def notes(self):
+232        """`str`: get or set entry notes"""
+233        return self._get_string_field('Notes')
+234
+235    @notes.setter
+236    def notes(self, value):
+237        return self._set_string_field('Notes', value)
+238
+239    @property
+240    def icon(self):
+241        """`str`: get or set entry icon. See `icons`"""
+242        return self._get_subelement_text('IconID')
+243
+244    @icon.setter
+245    def icon(self, value):
+246        return self._set_subelement_text('IconID', value)
+247
+248    @property
+249    def tags(self):
+250        """`str`: get or set entry tags"""
+251        val = self._get_subelement_text('Tags')
+252        return val.replace(',', ';').split(';') if val else []
+253
+254    @tags.setter
+255    def tags(self, value, sep=';'):
+256        # Accept both str or list
+257        v = sep.join(value if isinstance(value, list) else [value])
+258        return self._set_subelement_text('Tags', v)
+259
+260    @property
+261    def otp(self):
+262        """`str`: get or set entry OTP text. (defacto standard)"""
+263        return self._get_string_field('otp')
+264
+265    @otp.setter
+266    def otp(self, value):
+267        if self.otp:
+268            return self._set_string_field('otp', value)
+269        else:
+270            return self._set_string_field('otp', value, True)
+271
+272    @property
+273    def history(self):
+274        """`list` of `HistoryEntry`: get entry history"""
+275        if self._element.find('History') is not None:
+276            return [HistoryEntry(element=x, kp=self._kp) for x in self._element.find('History').findall('Entry')]
+277        else:
+278            return []
+279
+280    @history.setter
+281    def history(self, value):
+282        raise NotImplementedError()
+283
+284    @property
+285    def autotype_enabled(self):
+286        """bool: get or set autotype enabled state.  Determines whether `autotype_sequence` should be used"""
+287        enabled = self._element.find('AutoType/Enabled')
+288        if enabled.text is not None:
+289            return enabled.text == 'True'
+290
+291    @autotype_enabled.setter
+292    def autotype_enabled(self, value):
+293        enabled = self._element.find('AutoType/Enabled')
+294        if value is not None:
+295            enabled.text = str(value)
+296        else:
+297            enabled.text = None
+298
+299    @property
+300    def autotype_sequence(self):
+301        """str: get or set [autotype string](https://keepass.info/help/base/autotype.html)"""
+302        sequence = self._element.find('AutoType/DefaultSequence')
+303        if sequence is None or sequence.text == '':
+304            return None
+305        return sequence.text
+306
+307    @autotype_sequence.setter
+308    def autotype_sequence(self, value):
+309        self._element.find('AutoType/DefaultSequence').text = value
+310
+311    @property
+312    def autotype_window(self):
+313        """`str`: get or set [autotype target window filter](https://keepass.info/help/base/autotype.html#autowindows)"""
+314        sequence = self._element.find('AutoType/Association/Window')
+315        if sequence is None or sequence.text == '':
+316            return None
+317        return sequence.text
+318
+319    @autotype_window.setter
+320    def autotype_window(self, value):
+321        self._element.find('AutoType/Association/Window').text = value
+322
+323    @property
+324    def is_a_history_entry(self):
+325        """`bool`: check if entry is History entry"""
+326        parent = self._element.getparent()
+327        if parent is not None:
+328            return parent.tag == 'History'
+329        return False
+330
+331    @property
+332    def path(self):
+333        """`list` of (`str` or `None`): Path of entry.  List contains all parent group names
+334        ending with entry title. May contain `None` for unnamed/untitled groups/entries."""
+335
+336        # The root group is an orphan
+337        if self.parentgroup is None:
+338            return None
+339        p = self.parentgroup
+340        path = [self.title]
+341        while p is not None and not p.is_root_group:
+342            if p.name is not None:  # dont make the root group appear
+343                path.insert(0, p.name)
+344            p = p.parentgroup
+345        return path
+346
+347    def set_custom_property(self, key, value, protect=False):
+348        assert key not in reserved_keys, '{} is a reserved key'.format(key)
+349        return self._set_string_field(key, value, protect)
+350
+351    def get_custom_property(self, key):
+352        assert key not in reserved_keys, '{} is a reserved key'.format(key)
+353        return self._get_string_field(key)
+354
+355    def delete_custom_property(self, key):
+356        if key not in self._get_string_field_keys(exclude_reserved=True):
+357            raise AttributeError('No such key: {}'.format(key))
+358        prop = self._xpath('String/Key[text()="{}"]/..'.format(key), first=True)
+359        if prop is None:
+360            raise AttributeError('Could not find property element')
+361        self._element.remove(prop)
+362
+363    def is_custom_property_protected(self, key):
+364        """Whether a custom property is protected.
+365
+366        Return False if the entry does not have a custom property with the
+367        specified key.
+368
+369        Args:
+370            key (`str`): key of the custom property to check.
+371
+372        Returns:
+373            `bool`: Whether the custom property is protected.
+374
+375        """
+376        assert key not in reserved_keys, '{} is a reserved key'.format(key)
+377        return self._is_property_protected(key)
+378
+379    def _is_property_protected(self, key):
+380        """Whether a property is protected."""
+381        field = self._xpath('String/Key[text()="{}"]/../Value'.format(key), first=True)
+382        if field is not None:
+383            return field.attrib.get("Protected", "False") == "True"
+384        return False
+385
+386    @property
+387    def custom_properties(self):
+388        keys = self._get_string_field_keys(exclude_reserved=True)
+389        props = {}
+390        for k in keys:
+391            props[k] = self._get_string_field(k)
+392        return props
+393
+394    def ref(self, attribute):
+395        """Create reference to an attribute of this element.
+396
+397        Args:
+398            attribute (`str`): one of 'title', 'username', 'password', 'url', 'notes', or 'uuid'
+399
+400        Returns:
+401            `str`: [field reference][fieldref] to this field of this entry
+402
+403        [fieldref]: https://keepass.info/help/base/fieldrefs.html
+404        """
+405        attribute_to_field = {
+406            'title': 'T',
+407            'username': 'U',
+408            'password': 'P',
+409            'url': 'A',
+410            'notes': 'N',
+411            'uuid': 'I',
+412        }
+413        return '{{REF:{}@I:{}}}'.format(attribute_to_field[attribute], self.uuid.hex.upper())
+414
+415    def save_history(self):
+416        """
+417        Save the entry in its history.  History is not created unless this function is
+418        explicitly called.
+419        """
+420        archive = deepcopy(self._element)
+421        hist = archive.find('History')
+422        if hist is not None:
+423            archive.remove(hist)
+424            self._element.find('History').append(archive)
+425        else:
+426            history = Element('History')
+427            history.append(archive)
+428            self._element.append(history)
+429
+430    def delete_history(self, history_entry=None, all=False):
+431        """
+432        Delete entries from history
+433
+434        Args:
+435            history_entry (`Entry`): history item to delete
+436            all (`bool`): delete all entries from history.  Default is False
+437        """
+438
+439        if all:
+440            self._element.remove(self._element.find('History'))
+441        else:
+442            self._element.find('History').remove(history_entry._element)
+443
+444    def __str__(self):
+445        # filter out NoneTypes and join into string
+446        pathstr = '/'.join('' if p is None else p for p in self.path)
+447        return 'Entry: "{} ({})"'.format(pathstr, self.username)
+
+ + +

Entry and Group inherit from this class

+
+ + +
+ +
+ + Entry( title=None, username=None, password=None, url=None, notes=None, otp=None, tags=None, expires=False, expiry_time=None, icon=None, autotype_sequence=None, autotype_enabled=True, autotype_window=None, element=None, kp=None) + + + +
+ +
28    def __init__(self, title=None, username=None, password=None, url=None,
+29                 notes=None, otp=None, tags=None, expires=False, expiry_time=None,
+30                 icon=None, autotype_sequence=None, autotype_enabled=True, autotype_window=None,
+31                 element=None, kp=None):
+32
+33        self._kp = kp
+34
+35        if element is None:
+36            super().__init__(
+37                element=Element('Entry'),
+38                kp=kp,
+39                expires=expires,
+40                expiry_time=expiry_time,
+41                icon=icon
+42            )
+43            self._element.append(E.String(E.Key('Title'), E.Value(title)))
+44            self._element.append(E.String(E.Key('UserName'), E.Value(username)))
+45            self._element.append(
+46                E.String(E.Key('Password'), E.Value(password, Protected="True"))
+47            )
+48            if url:
+49                self._element.append(E.String(E.Key('URL'), E.Value(url)))
+50            if notes:
+51                self._element.append(E.String(E.Key('Notes'), E.Value(notes)))
+52            if otp:
+53                self._element.append(
+54                    E.String(E.Key('otp'), E.Value(otp, Protected="True"))
+55                )
+56            if tags:
+57                self._element.append(
+58                    E.Tags(';'.join(tags) if isinstance(tags, list) else tags)
+59                )
+60            self._element.append(
+61                E.AutoType(
+62                    E.Enabled(str(autotype_enabled)),
+63                    E.DataTransferObfuscation('0'),
+64                    E.DefaultSequence(str(autotype_sequence) if autotype_sequence else ''),
+65                    E.Association(
+66                        E.Window(str(autotype_window) if autotype_window else ''),
+67                        E.KeystrokeSequence('')
+68                    )
+69                )
+70            )
+71            # FIXME: include custom_properties in constructor
+72
+73        else:
+74            assert type(element) in [_Element, Element, ObjectifiedElement], \
+75                'The provided element is not an LXML Element, but a {}'.format(
+76                    type(element)
+77                )
+78            assert element.tag == 'Entry', 'The provided element is not an Entry '\
+79                'element, but a {}'.format(element.tag)
+80            self._element = element
+
+ + + + +
+
+ +
+ index + + + +
+ +
133    @property
+134    def index(self):
+135        """`int`: index of a entry within a group"""
+136        group = self.group._element
+137        children = group.getchildren()
+138        first_index = self.group._first_entry
+139        index = children.index(self._element)
+140        return index - first_index
+
+ + +

int: index of a entry within a group

+
+ + +
+
+ +
+ + def + reindex(self, new_index): + + + +
+ +
142    def reindex(self, new_index):
+143        """Move entry to a new index within a group
+144        
+145        Args:
+146            new_index (`int`): new index for the entry starting at 0
+147        """
+148        group = self.group._element
+149        first_index = self.group._first_entry
+150        group.remove(self._element)
+151        group.insert(new_index+first_index, self._element)
+
+ + +

Move entry to a new index within a group

+ +
Arguments:
+ +
    +
  • new_index (int): new index for the entry starting at 0
  • +
+
+ + +
+
+ +
+ attachments + + + +
+ +
153    @property
+154    def attachments(self):
+155        """`list` of `Attachment`: attachments associated with entry"""
+156        return self._kp.find_attachments(
+157            element=self,
+158            filename='.*',
+159            regex=True,
+160            recursive=False
+161        )
+
+ + +

list of Attachment: attachments associated with entry

+
+ + +
+
+ +
+ + def + add_attachment(self, id, filename): + + + +
+ +
163    def add_attachment(self, id, filename):
+164        """Add attachment to entry
+165
+166        The existence of a binary with the given `id` is not checked
+167
+168        Args:
+169            id (`int`): ID of attachment in database
+170            filename (`str`): filename to assign to this attachment data
+171
+172        Returns:
+173            `Attachment`
+174        """
+175        element = E.Binary(
+176            E.Key(filename),
+177            E.Value(Ref=str(id))
+178        )
+179        self._element.append(element)
+180
+181        return attachment.Attachment(element=element, kp=self._kp)
+
+ + +

Add attachment to entry

+ +

The existence of a binary with the given id is not checked

+ +
Arguments:
+ +
    +
  • id (int): ID of attachment in database
  • +
  • filename (str): filename to assign to this attachment data
  • +
+ +
Returns:
+ +
+

Attachment

+
+
+ + +
+
+ +
+ + def + delete_attachment(self, attachment): + + + +
+ +
183    def delete_attachment(self, attachment):
+184        """remove an attachment from entry.  Does not remove binary data"""
+185        attachment.delete()
+
+ + +

remove an attachment from entry. Does not remove binary data

+
+ + +
+
+ +
+ + def + deref(self, attribute): + + + +
+ +
187    def deref(self, attribute):
+188        """See `PyKeePass.deref`"""
+189        return self._kp.deref(getattr(self, attribute))
+
+ + + + + +
+
+ +
+ title + + + +
+ +
191    @property
+192    def title(self):
+193        """`str`: get or set entry title"""
+194        return self._get_string_field('Title')
+
+ + +

str: get or set entry title

+
+ + +
+
+ +
+ username + + + +
+ +
200    @property
+201    def username(self):
+202        """`str`: get or set entry username"""
+203        return self._get_string_field('UserName')
+
+ + +

str: get or set entry username

+
+ + +
+
+ +
+ password + + + +
+ +
209    @property
+210    def password(self):
+211        """`str`: get or set entry password"""
+212        return self._get_string_field('Password')
+
+ + +

str: get or set entry password

+
+ + +
+
+ +
+ url + + + +
+ +
221    @property
+222    def url(self):
+223        """str: get or set entry URL"""
+224        return self._get_string_field('URL')
+
+ + +

str: get or set entry URL

+
+ + +
+
+ +
+ notes + + + +
+ +
230    @property
+231    def notes(self):
+232        """`str`: get or set entry notes"""
+233        return self._get_string_field('Notes')
+
+ + +

str: get or set entry notes

+
+ + +
+
+ +
+ icon + + + +
+ +
239    @property
+240    def icon(self):
+241        """`str`: get or set entry icon. See `icons`"""
+242        return self._get_subelement_text('IconID')
+
+ + +

str: get or set entry icon. See icons

+
+ + +
+
+ +
+ tags + + + +
+ +
248    @property
+249    def tags(self):
+250        """`str`: get or set entry tags"""
+251        val = self._get_subelement_text('Tags')
+252        return val.replace(',', ';').split(';') if val else []
+
+ + +

str: get or set entry tags

+
+ + +
+
+ +
+ otp + + + +
+ +
260    @property
+261    def otp(self):
+262        """`str`: get or set entry OTP text. (defacto standard)"""
+263        return self._get_string_field('otp')
+
+ + +

str: get or set entry OTP text. (defacto standard)

+
+ + +
+
+ +
+ history + + + +
+ +
272    @property
+273    def history(self):
+274        """`list` of `HistoryEntry`: get entry history"""
+275        if self._element.find('History') is not None:
+276            return [HistoryEntry(element=x, kp=self._kp) for x in self._element.find('History').findall('Entry')]
+277        else:
+278            return []
+
+ + +

list of HistoryEntry: get entry history

+
+ + +
+
+ +
+ autotype_enabled + + + +
+ +
284    @property
+285    def autotype_enabled(self):
+286        """bool: get or set autotype enabled state.  Determines whether `autotype_sequence` should be used"""
+287        enabled = self._element.find('AutoType/Enabled')
+288        if enabled.text is not None:
+289            return enabled.text == 'True'
+
+ + +

bool: get or set autotype enabled state. Determines whether autotype_sequence should be used

+
+ + +
+
+ +
+ autotype_sequence + + + +
+ +
299    @property
+300    def autotype_sequence(self):
+301        """str: get or set [autotype string](https://keepass.info/help/base/autotype.html)"""
+302        sequence = self._element.find('AutoType/DefaultSequence')
+303        if sequence is None or sequence.text == '':
+304            return None
+305        return sequence.text
+
+ + +

str: get or set autotype string

+
+ + +
+
+ +
+ autotype_window + + + +
+ +
311    @property
+312    def autotype_window(self):
+313        """`str`: get or set [autotype target window filter](https://keepass.info/help/base/autotype.html#autowindows)"""
+314        sequence = self._element.find('AutoType/Association/Window')
+315        if sequence is None or sequence.text == '':
+316            return None
+317        return sequence.text
+
+ + + + + +
+
+ +
+ is_a_history_entry + + + +
+ +
323    @property
+324    def is_a_history_entry(self):
+325        """`bool`: check if entry is History entry"""
+326        parent = self._element.getparent()
+327        if parent is not None:
+328            return parent.tag == 'History'
+329        return False
+
+ + +

bool: check if entry is History entry

+
+ + +
+
+ +
+ path + + + +
+ +
331    @property
+332    def path(self):
+333        """`list` of (`str` or `None`): Path of entry.  List contains all parent group names
+334        ending with entry title. May contain `None` for unnamed/untitled groups/entries."""
+335
+336        # The root group is an orphan
+337        if self.parentgroup is None:
+338            return None
+339        p = self.parentgroup
+340        path = [self.title]
+341        while p is not None and not p.is_root_group:
+342            if p.name is not None:  # dont make the root group appear
+343                path.insert(0, p.name)
+344            p = p.parentgroup
+345        return path
+
+ + +

list of (str or None): Path of entry. List contains all parent group names +ending with entry title. May contain None for unnamed/untitled groups/entries.

+
+ + +
+
+ +
+ + def + set_custom_property(self, key, value, protect=False): + + + +
+ +
347    def set_custom_property(self, key, value, protect=False):
+348        assert key not in reserved_keys, '{} is a reserved key'.format(key)
+349        return self._set_string_field(key, value, protect)
+
+ + + + +
+
+ +
+ + def + get_custom_property(self, key): + + + +
+ +
351    def get_custom_property(self, key):
+352        assert key not in reserved_keys, '{} is a reserved key'.format(key)
+353        return self._get_string_field(key)
+
+ + + + +
+
+ +
+ + def + delete_custom_property(self, key): + + + +
+ +
355    def delete_custom_property(self, key):
+356        if key not in self._get_string_field_keys(exclude_reserved=True):
+357            raise AttributeError('No such key: {}'.format(key))
+358        prop = self._xpath('String/Key[text()="{}"]/..'.format(key), first=True)
+359        if prop is None:
+360            raise AttributeError('Could not find property element')
+361        self._element.remove(prop)
+
+ + + + +
+
+ +
+ + def + is_custom_property_protected(self, key): + + + +
+ +
363    def is_custom_property_protected(self, key):
+364        """Whether a custom property is protected.
+365
+366        Return False if the entry does not have a custom property with the
+367        specified key.
+368
+369        Args:
+370            key (`str`): key of the custom property to check.
+371
+372        Returns:
+373            `bool`: Whether the custom property is protected.
+374
+375        """
+376        assert key not in reserved_keys, '{} is a reserved key'.format(key)
+377        return self._is_property_protected(key)
+
+ + +

Whether a custom property is protected.

+ +

Return False if the entry does not have a custom property with the +specified key.

+ +
Arguments:
+ +
    +
  • key (str): key of the custom property to check.
  • +
+ +
Returns:
+ +
+

bool: Whether the custom property is protected.

+
+
+ + +
+
+ +
+ custom_properties + + + +
+ +
386    @property
+387    def custom_properties(self):
+388        keys = self._get_string_field_keys(exclude_reserved=True)
+389        props = {}
+390        for k in keys:
+391            props[k] = self._get_string_field(k)
+392        return props
+
+ + + + +
+
+ +
+ + def + ref(self, attribute): + + + +
+ +
394    def ref(self, attribute):
+395        """Create reference to an attribute of this element.
+396
+397        Args:
+398            attribute (`str`): one of 'title', 'username', 'password', 'url', 'notes', or 'uuid'
+399
+400        Returns:
+401            `str`: [field reference][fieldref] to this field of this entry
+402
+403        [fieldref]: https://keepass.info/help/base/fieldrefs.html
+404        """
+405        attribute_to_field = {
+406            'title': 'T',
+407            'username': 'U',
+408            'password': 'P',
+409            'url': 'A',
+410            'notes': 'N',
+411            'uuid': 'I',
+412        }
+413        return '{{REF:{}@I:{}}}'.format(attribute_to_field[attribute], self.uuid.hex.upper())
+
+ + +

Create reference to an attribute of this element.

+ +
Arguments:
+ +
    +
  • attribute (str): one of 'title', 'username', 'password', 'url', 'notes', or 'uuid'
  • +
+ +
Returns:
+ +
+

str: field reference to this field of this entry

+
+
+ + +
+
+ +
+ + def + save_history(self): + + + +
+ +
415    def save_history(self):
+416        """
+417        Save the entry in its history.  History is not created unless this function is
+418        explicitly called.
+419        """
+420        archive = deepcopy(self._element)
+421        hist = archive.find('History')
+422        if hist is not None:
+423            archive.remove(hist)
+424            self._element.find('History').append(archive)
+425        else:
+426            history = Element('History')
+427            history.append(archive)
+428            self._element.append(history)
+
+ + +

Save the entry in its history. History is not created unless this function is +explicitly called.

+
+ + +
+
+ +
+ + def + delete_history(self, history_entry=None, all=False): + + + +
+ +
430    def delete_history(self, history_entry=None, all=False):
+431        """
+432        Delete entries from history
+433
+434        Args:
+435            history_entry (`Entry`): history item to delete
+436            all (`bool`): delete all entries from history.  Default is False
+437        """
+438
+439        if all:
+440            self._element.remove(self._element.find('History'))
+441        else:
+442            self._element.find('History').remove(history_entry._element)
+
+ + +

Delete entries from history

+ +
Arguments:
+ +
    +
  • history_entry (Entry): history item to delete
  • +
  • all (bool): delete all entries from history. Default is False
  • +
+
+ + +
+
+
+ +
+ + class + Group(pykeepass.baseelement.BaseElement): + + + +
+ +
 10class Group(BaseElement):
+ 11
+ 12    def __init__(self, name=None, element=None, icon=None, notes=None,
+ 13                 kp=None, expires=None, expiry_time=None):
+ 14
+ 15        self._kp = kp
+ 16
+ 17        if element is None:
+ 18            super().__init__(
+ 19                element=Element('Group'),
+ 20                kp=kp,
+ 21                expires=expires,
+ 22                expiry_time=expiry_time,
+ 23                icon=icon
+ 24            )
+ 25            self._element.append(E.Name(name))
+ 26            if notes:
+ 27                self._element.append(E.Notes(notes))
+ 28
+ 29        else:
+ 30            assert type(element) in [_Element, Element, ObjectifiedElement], \
+ 31                'The provided element is not an LXML Element, but {}'.format(
+ 32                    type(element)
+ 33                )
+ 34            assert element.tag == 'Group', 'The provided element is not a Group '\
+ 35                'element, but a {}'.format(element.tag)
+ 36            self._element = element
+ 37
+ 38    @property
+ 39    def _first_entry(self):
+ 40        children = self._element.getchildren()
+ 41        first_element = next(e for e in children if e.tag == "Entry")
+ 42        return children.index(first_element)
+ 43
+ 44    @property
+ 45    def name(self):
+ 46        """`str`: get or set group name"""
+ 47        return self._get_subelement_text('Name')
+ 48
+ 49    @name.setter
+ 50    def name(self, value):
+ 51        return self._set_subelement_text('Name', value)
+ 52
+ 53    @property
+ 54    def notes(self):
+ 55        """`str`: get or set group notes"""
+ 56        return self._get_subelement_text('Notes')
+ 57
+ 58    @notes.setter
+ 59    def notes(self, value):
+ 60        return self._set_subelement_text('Notes', value)
+ 61
+ 62    @property
+ 63    def entries(self):
+ 64        """`list` of `Entry`: get list of entries in this group"""
+ 65        return [Entry(element=x, kp=self._kp) for x in self._element.findall('Entry')]
+ 66
+ 67    @property
+ 68    def subgroups(self):
+ 69        """`list` of `Group`: get list of groups in this group"""
+ 70        return [Group(element=x, kp=self._kp) for x in self._element.findall('Group')]
+ 71
+ 72    @property
+ 73    def is_root_group(self):
+ 74        """`bool`: return True if this is the database root"""
+ 75        return self._element.getparent().tag == 'Root'
+ 76
+ 77    @property
+ 78    def path(self):
+ 79        """`list` of (`str` or `None`): names of all parent groups, not including root"""
+ 80        # The root group is an orphan
+ 81        if self.is_root_group or self.parentgroup is None:
+ 82            return []
+ 83        p = self.parentgroup
+ 84        path = [self.name]
+ 85        while p is not None and not p.is_root_group:
+ 86            if p.name is not None:  # dont make the root group appear
+ 87                path.insert(0, p.name)
+ 88            p = p.parentgroup
+ 89        return path
+ 90
+ 91    def append(self, entries):
+ 92        """Add copy of an entry to this group
+ 93
+ 94        Args:
+ 95            entries (`Entry` or `list` of `Entry`)
+ 96        """
+ 97        # FIXME: check if `entries` is iterable instead of list
+ 98        if isinstance(entries, list):
+ 99            for e in entries:
+100                self._element.append(e._element)
+101        else:
+102            self._element.append(entries._element)
+103
+104    def __str__(self):
+105        # filter out NoneTypes and join into string
+106        pathstr = '/'.join('' if p is None else p for p in self.path)
+107        return 'Group: "{}"'.format(pathstr)
+
+ + +

Entry and Group inherit from this class

+
+ + +
+ +
+ + Group( name=None, element=None, icon=None, notes=None, kp=None, expires=None, expiry_time=None) + + + +
+ +
12    def __init__(self, name=None, element=None, icon=None, notes=None,
+13                 kp=None, expires=None, expiry_time=None):
+14
+15        self._kp = kp
+16
+17        if element is None:
+18            super().__init__(
+19                element=Element('Group'),
+20                kp=kp,
+21                expires=expires,
+22                expiry_time=expiry_time,
+23                icon=icon
+24            )
+25            self._element.append(E.Name(name))
+26            if notes:
+27                self._element.append(E.Notes(notes))
+28
+29        else:
+30            assert type(element) in [_Element, Element, ObjectifiedElement], \
+31                'The provided element is not an LXML Element, but {}'.format(
+32                    type(element)
+33                )
+34            assert element.tag == 'Group', 'The provided element is not a Group '\
+35                'element, but a {}'.format(element.tag)
+36            self._element = element
+
+ + + + +
+
+ +
+ name + + + +
+ +
44    @property
+45    def name(self):
+46        """`str`: get or set group name"""
+47        return self._get_subelement_text('Name')
+
+ + +

str: get or set group name

+
+ + +
+
+ +
+ notes + + + +
+ +
53    @property
+54    def notes(self):
+55        """`str`: get or set group notes"""
+56        return self._get_subelement_text('Notes')
+
+ + +

str: get or set group notes

+
+ + +
+
+ +
+ entries + + + +
+ +
62    @property
+63    def entries(self):
+64        """`list` of `Entry`: get list of entries in this group"""
+65        return [Entry(element=x, kp=self._kp) for x in self._element.findall('Entry')]
+
+ + +

list of Entry: get list of entries in this group

+
+ + +
+
+ +
+ subgroups + + + +
+ +
67    @property
+68    def subgroups(self):
+69        """`list` of `Group`: get list of groups in this group"""
+70        return [Group(element=x, kp=self._kp) for x in self._element.findall('Group')]
+
+ + +

list of Group: get list of groups in this group

+
+ + +
+
+ +
+ is_root_group + + + +
+ +
72    @property
+73    def is_root_group(self):
+74        """`bool`: return True if this is the database root"""
+75        return self._element.getparent().tag == 'Root'
+
+ + +

bool: return True if this is the database root

+
+ + +
+
+ +
+ path + + + +
+ +
77    @property
+78    def path(self):
+79        """`list` of (`str` or `None`): names of all parent groups, not including root"""
+80        # The root group is an orphan
+81        if self.is_root_group or self.parentgroup is None:
+82            return []
+83        p = self.parentgroup
+84        path = [self.name]
+85        while p is not None and not p.is_root_group:
+86            if p.name is not None:  # dont make the root group appear
+87                path.insert(0, p.name)
+88            p = p.parentgroup
+89        return path
+
+ + +

list of (str or None): names of all parent groups, not including root

+
+ + +
+
+ +
+ + def + append(self, entries): + + + +
+ +
 91    def append(self, entries):
+ 92        """Add copy of an entry to this group
+ 93
+ 94        Args:
+ 95            entries (`Entry` or `list` of `Entry`)
+ 96        """
+ 97        # FIXME: check if `entries` is iterable instead of list
+ 98        if isinstance(entries, list):
+ 99            for e in entries:
+100                self._element.append(e._element)
+101        else:
+102            self._element.append(entries._element)
+
+ + +

Add copy of an entry to this group

+ +
Arguments:
+ + +
+ + +
+
+
+ +
+ + class + Attachment: + + + +
+ +
 6class Attachment:
+ 7    """Binary data attached to an `Entry`.
+ 8
+ 9    *Binary* refers to the bytes of the attached data
+10    (stored at the root level of the database), while *attachment* is a
+11    reference to a binary (stored in an entry).  A binary can be referenced
+12    by none, one or many attachments.
+13    A piece of binary data may be attached to multiple entries
+14
+15    """
+16    def __init__(self, element=None, kp=None, id=None, filename=None):
+17        self._element = element
+18        self._kp = kp
+19
+20    def __repr__(self):
+21        return "Attachment: '{}' -> {}".format(self.filename, self.id)
+22
+23    @property
+24    def id(self):
+25        """`str`: get or set id of binary the attachment points to"""
+26        return int(self._element.find('Value').attrib['Ref'])
+27
+28    @id.setter
+29    def id(self, id):
+30        self._element.find('Value').attrib['Ref'] = str(id)
+31
+32    @property
+33    def filename(self):
+34        """`str`: get or set filename string"""
+35        return self._element.find('Key').text
+36
+37    @filename.setter
+38    def filename(self, filename):
+39        self._element.find('Key').text = filename
+40
+41    @property
+42    def entry(self):
+43        """`Entry`: entry this attachment is associated with"""
+44        ancestor = self._element.getparent()
+45        return entry.Entry(element=ancestor, kp=self._kp)
+46
+47    @property
+48    def binary(self):
+49        """`bytes`: binary data this attachment points to"""
+50        try:
+51            return self._kp.binaries[self.id]
+52        except IndexError:
+53            raise BinaryError('No such binary with id {}'.format(self.id))
+54
+55    data = binary
+56
+57    def delete(self):
+58        """delete this attachment"""
+59        self._element.getparent().remove(self._element)
+
+ + +

Binary data attached to an Entry.

+ +

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. +A piece of binary data may be attached to multiple entries

+
+ + +
+ +
+ + Attachment(element=None, kp=None, id=None, filename=None) + + + +
+ +
16    def __init__(self, element=None, kp=None, id=None, filename=None):
+17        self._element = element
+18        self._kp = kp
+
+ + + + +
+
+ +
+ id + + + +
+ +
23    @property
+24    def id(self):
+25        """`str`: get or set id of binary the attachment points to"""
+26        return int(self._element.find('Value').attrib['Ref'])
+
+ + +

str: get or set id of binary the attachment points to

+
+ + +
+
+ +
+ filename + + + +
+ +
32    @property
+33    def filename(self):
+34        """`str`: get or set filename string"""
+35        return self._element.find('Key').text
+
+ + +

str: get or set filename string

+
+ + +
+
+ +
+ entry + + + +
+ +
41    @property
+42    def entry(self):
+43        """`Entry`: entry this attachment is associated with"""
+44        ancestor = self._element.getparent()
+45        return entry.Entry(element=ancestor, kp=self._kp)
+
+ + +

Entry: entry this attachment is associated with

+
+ + +
+
+ +
+ binary + + + +
+ +
47    @property
+48    def binary(self):
+49        """`bytes`: binary data this attachment points to"""
+50        try:
+51            return self._kp.binaries[self.id]
+52        except IndexError:
+53            raise BinaryError('No such binary with id {}'.format(self.id))
+
+ + +

bytes: binary data this attachment points to

+
+ + +
+
+ +
+ data + + + +
+ +
47    @property
+48    def binary(self):
+49        """`bytes`: binary data this attachment points to"""
+50        try:
+51            return self._kp.binaries[self.id]
+52        except IndexError:
+53            raise BinaryError('No such binary with id {}'.format(self.id))
+
+ + +

bytes: binary data this attachment points to

+
+ + +
+
+ +
+ + def + delete(self): + + + +
+ +
57    def delete(self):
+58        """delete this attachment"""
+59        self._element.getparent().remove(self._element)
+
+ + +

delete this attachment

+
+ + +
+
+
+
+ icons = + + {'Key': '0', 'World': '1', 'Warning': '2', 'NetworkServer': '3', 'MarkedDirectory': '4', 'UserCommunication': '5', 'Parts': '6', 'Notepad': '7', 'WorldSocket': '8', 'Identity': '9', 'PaperReady': '10', 'Digicam': '11', 'IRCommunication': '12', 'MultiKeys': '13', 'Energy': '14', 'Scanner': '15', 'WorldStar': '16', 'CDRom': '17', 'Monitor': '18', 'EMail': '19', 'Configuration': '20', 'ClipboardReady': '21', 'PaperNew': '22', 'Screen': '23', 'EnergyCareful': '24', 'EMailBox': '25', 'Disk': '26', 'Drive': '27', 'PaperQ': '28', 'TerminalEncrypted': '29', 'Console': '30', 'Printer': '31', 'ProgramIcons': '32', 'Run': '33', 'Settings': '34', 'WorldComputer': '35', 'Archive': '36', 'Homebanking': '37', 'DriveWindows': '38', 'Clock': '39', 'EMailSearch': '40', 'PaperFlag': '41', 'Memory': '42', 'TrashBin': '43', 'Note': '44', 'Expired': '45', 'Info': '46', 'Package': '47', 'Folder': '48', 'FolderOpen': '49', 'FolderPackage': '50', 'LockOpen': '51', 'PaperLocked': '52', 'Checked': '53', 'Pen': '54', 'Thumbnail': '55', 'Book': '56', 'List': '57', 'UserKey': '58', 'Tool': '59', 'Home': '60', 'Star': '61', 'Tux': '62', 'Feather': '63', 'Apple': '64', 'Wiki': '65', 'Money': '66', 'Certificate': '67', 'BlackBerry': '68', 'KEY': '0', 'GLOBE': '1', 'WARNING_SIGN': '2', 'SERVER': '3', 'PINNED_NOTE': '4', 'SPEECH_BUBBLE': '5', 'SQUARES': '6', 'HANDWRITTEN_NOTE': '7', 'GLOBE_PLUG': '8', 'BUSINESS_CARD': '9', 'GREEN_AND_WHITE_THINGY': '10', 'CAMERA': '11', 'INFRARED': '12', 'KEYS': '13', 'POWER_PLUG': '14', 'FLATBED_SCANNER': '15', 'GLOBE_STAR': '16', 'CD_ROM': '17', 'MONITOR': '18', 'ENVELOPE': '19', 'GEAR': '20', 'CHECKLIST': '21', 'NOTEPAD': '22', 'DESKTOP': '23', 'POWER_FLASH': '24', 'FOLDER_ENVELOPE': '25', 'FLOPPY_DISK': '26', 'SERVER_2': '27', 'GREEN_DOT': '28', 'MONITOR_KEY': '29', 'SHELL': '30', 'PRINTER': '31', 'DASHBOARD': '32', 'CROATIA': '33', 'WRENCH': '34', 'PC_INTERNET': '35', 'ZIP_FILE': '36', 'PERCENT_SIGN': '37', 'SAMBA_SHARE': '38', 'CLOCK': '39', 'SEARCH': '40', 'USED_TAMPON': '41', 'MEMORY_STICK': '42', 'RECYCLE_BIN': '43', 'POST_IT': '44', 'RED_CROSS': '45', 'INFO_SIGN': '46', 'CARDBOARD': '47', 'FOLDER': '48', 'FOLDER_OPEN': '49', 'FOLDER_CUBE': '50', 'LOCK_OPEN': '51', 'LOCK_CLOSED': '52', 'GREEN_CHECKMARK': '53', 'FEATHER_PEN': '54', 'POLAROID_PICTURE': '55', 'BOOK_OPENED': '56', 'UI': '57', 'MANAGER': '58', 'HAMMER': '59', 'HOUSE': '60', 'STAR': '61', 'PENGUIN': '62', 'FEATHER': '63', 'APPLE': '64', 'WIKIPEDIA': '65', 'DOLLAR_SIGN': '66', 'CERTIFICATE': '67', 'SMARTPHONE': '68'} + + +
+ + + + +
+
+ +
+ + def + create_database(filename, password=None, keyfile=None, transformed_key=None): + + + +
+ +
1033def create_database(
+1034        filename, password=None, keyfile=None, transformed_key=None
+1035):
+1036    """
+1037    Create a new database at ``filename`` with supplied credentials.
+1038
+1039    Args:
+1040        filename (`str`, optional): path to database or stream object.
+1041            If None, the path given when the database was opened is used.
+1042        password (`str`, optional): database password.  If None,
+1043            database is assumed to have no password
+1044        keyfile (`str`, optional): path to keyfile.  If None,
+1045            database is assumed to have no keyfile
+1046        transformed_key (`bytes`, optional): precomputed transformed
+1047            key.
+1048
+1049    Returns:
+1050        `PyKeePass`
+1051    """
+1052    keepass_instance = PyKeePass(
+1053        BLANK_DATABASE_LOCATION, BLANK_DATABASE_PASSWORD
+1054    )
+1055
+1056    keepass_instance.filename = filename
+1057    keepass_instance.password = password
+1058    keepass_instance.keyfile = keyfile
+1059
+1060    keepass_instance.save(transformed_key)
+1061    return keepass_instance
+
+ + +

Create a new database at filename with supplied credentials.

+ +
Arguments:
+ +
    +
  • filename (str, optional): path to database or stream object. +If None, the path given when the database was opened is used.
  • +
  • password (str, optional): database password. If None, +database is assumed to have no password
  • +
  • keyfile (str, optional): path to keyfile. If None, +database is assumed to have no keyfile
  • +
  • transformed_key (bytes, optional): precomputed transformed +key.
  • +
+ +
Returns:
+ +
+

PyKeePass

+
+
+ + +
+
+
+ __version__ = +'4.1.0.post1' + + +
+ + + + +
+
+ + \ No newline at end of file diff --git a/pykeepass.md b/pykeepass.md deleted file mode 100644 index c70ad44c..00000000 --- a/pykeepass.md +++ /dev/null @@ -1,809 +0,0 @@ - - - - -# module `pykeepass` - - - - -**Global Variables** ---------------- -- **baseelement** -- **group**: # FIXME python2 - -- **entry**: # FIXME python2 - -- **exceptions**: # ----- binary parsing exceptions ----- - -- **attachment**: # FIXME python2 - -- **kdbx_parsing** -- **xpath**: # FIXME python2 - -- **pykeepass**: # coding: utf-8 - -- **version** -- **kdf_uuids** -- **attachment_xp** -- **entry_xp** -- **group_xp** -- **path_xp** -- **BLANK_DATABASE_FILENAME** -- **BLANK_DATABASE_LOCATION** -- **BLANK_DATABASE_PASSWORD** - ---- - - - -## function `create_database` - -```python -create_database(filename, password=None, keyfile=None, transformed_key=None) -``` - - - - - - ---- - - - -## function `debug_setup` - -```python -debug_setup() -``` - -Convenience function to quickly enable debug messages - - ---- - - - -## class `PyKeePass` -Open a KeePass database - - - -**Args:** - - - `filename` (:obj:`str`, optional): path to database or stream object. If None, the path given when the database was opened is used. - - `password` (:obj:`str`, optional): database password. If None, database is assumed to have no password - - `keyfile` (:obj:`str`, optional): path to keyfile. If None, database is assumed to have no keyfile - - `transformed_key` (:obj:`bytes`, optional): precomputed transformed key. - - - -**Raises:** - - - `CredentialsError`: raised when password/keyfile or transformed key are wrong - - `HeaderChecksumError`: raised when checksum in database header is is wrong. e.g. database tampering or file corruption - - `PayloadChecksumError`: raised when payload blocks checksum is wrong, e.g. corruption during database saving - - - -**Todo:** - - - raise, no filename provided, database not open - - - -### method `__init__` - -```python -__init__(filename, password=None, keyfile=None, transformed_key=None) -``` - - - - - - ---- - -#### property attachments - - - - - ---- - -#### property binaries - - - - - ---- - -#### property credchange_date - - - - - ---- - -#### property credchange_recommended - - - - - ---- - -#### property credchange_recommended_days - -Days until password update should be recommended - ---- - -#### property credchange_required - - - - - ---- - -#### property credchange_required_days - -Days until password update should be required - ---- - -#### property encryption_algorithm - -str: encryption algorithm used by database during decryption. Can be one of 'aes256', 'chacha20', or 'twofish'. - ---- - -#### property entries - -:obj:`list` of :obj:`Entry`: list of all Entry objects in database, excluding history - ---- - -#### property groups - -:obj:`list` of :obj:`Group`: list of all Group objects in database - - - ---- - -#### property kdf_algorithm - -str: key derivation algorithm used by database during decryption. Can be one of 'aeskdf', 'argon2', or 'aeskdf' - ---- - -#### property keyfile - - - - - ---- - -#### property password - - - - - ---- - -#### property recyclebin_group - -Group: RecycleBin Group of database - ---- - -#### property root_group - -Group: root Group of database - ---- - -#### property transformed_key - -bytes: transformed key used in database decryption. May be cached and passed to `open` for faster database opening - ---- - -#### property tree - -lxml.etree._ElementTree: database XML payload - ---- - -#### property version - -tuple: Length 2 tuple of ints containing major and minor versions. Generally (3, 1) or (4, 0). - - - ---- - - - -### method `add_binary` - -```python -add_binary(data, compressed=True, protected=True) -``` - - - - - ---- - - - -### method `add_entry` - -```python -add_entry( - destination_group, - title, - username, - password, - url=None, - notes=None, - expiry_time=None, - tags=None, - otp=None, - icon=None, - force_creation=False -) -``` - - - - - ---- - - - -### method `add_group` - -```python -add_group(destination_group, group_name, icon=None, notes=None) -``` - - - - - ---- - - - -### method `delete_binary` - -```python -delete_binary(id) -``` - - - - - ---- - - - -### method `delete_entry` - -```python -delete_entry(entry) -``` - - - - - ---- - - - -### method `delete_group` - -```python -delete_group(group) -``` - - - - - ---- - - - -### method `deref` - -```python -deref(value) -``` - - - - - ---- - - - -### method `dump_xml` - -```python -dump_xml(filename) -``` - -Dump the contents of the database to file as XML - - - -**Args:** - - - `filename` (str): path to output file - ---- - - - -### method `empty_group` - -```python -empty_group(group) -``` - -Delete the content of a group. - -This does not delete the group itself - - - -**Args:** - - - `group` (:obj:`Group`): Group to empty - ---- - - - -### method `find_attachments` - -```python -find_attachments(recursive=True, path=None, element=None, **kwargs) -``` - - - - - ---- - - - -### method `find_entries` - -```python -find_entries(recursive=True, path=None, group=None, **kwargs) -``` - - - - - ---- - - - -### method `find_entries_by_notes` - -```python -find_entries_by_notes( - notes, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `find_entries_by_password` - -```python -find_entries_by_password( - password, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `find_entries_by_path` - -```python -find_entries_by_path( - path, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `find_entries_by_string` - -```python -find_entries_by_string( - string, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `find_entries_by_title` - -```python -find_entries_by_title( - title, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `find_entries_by_url` - -```python -find_entries_by_url( - url, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `find_entries_by_username` - -```python -find_entries_by_username( - username, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `find_entries_by_uuid` - -```python -find_entries_by_uuid( - uuid, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `find_groups` - -```python -find_groups(recursive=True, path=None, group=None, **kwargs) -``` - - - - - ---- - - - -### method `find_groups_by_name` - -```python -find_groups_by_name( - group_name, - regex=False, - flags=None, - group=None, - first=False -) -``` - - - - - ---- - - - -### method `find_groups_by_notes` - -```python -find_groups_by_notes( - notes, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `find_groups_by_path` - -```python -find_groups_by_path( - group_path_str=None, - regex=False, - flags=None, - group=None, - first=False -) -``` - - - - - ---- - - - -### method `find_groups_by_uuid` - -```python -find_groups_by_uuid( - uuid, - regex=False, - flags=None, - group=None, - history=False, - first=False -) -``` - - - - - ---- - - - -### method `move_entry` - -```python -move_entry(entry, destination_group) -``` - - - - - ---- - - - -### method `move_group` - -```python -move_group(group, destination_group) -``` - - - - - ---- - - - -### method `read` - -```python -read(filename=None, password=None, keyfile=None, transformed_key=None) -``` - -See class docstring. - - - -**Todo:** - - - raise, no filename provided, database not open - ---- - - - -### method `reload` - -```python -reload() -``` - -Reload current database using previous credentials - ---- - - - -### method `save` - -```python -save(filename=None, transformed_key=None) -``` - -Save current database object to disk. - - - -**Args:** - - - `filename` (:obj:`str`, optional): path to database or stream object. If None, the path given when the database was opened is used. PyKeePass.filename is unchanged. - - `transformed_key` (:obj:`bytes`, optional): precomputed transformed key. - ---- - - - -### method `trash_entry` - -```python -trash_entry(entry) -``` - -Move an entry to the RecycleBin - - - -**Args:** - - - `entry` (:obj:`Entry`): Entry to send to the RecycleBin - ---- - - - -### method `trash_group` - -```python -trash_group(group) -``` - -Move a group to the RecycleBin - - - -**Args:** - - - `group` (:obj:`Group`): Group to send to the RecycleBin - ---- - - - -### method `xml` - -```python -xml() -``` - -Get XML part of database as string - - - -**Returns:** - - - `str`: XML payload section of database. - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/setters.md b/setters.md deleted file mode 100644 index 6989f45f..00000000 --- a/setters.md +++ /dev/null @@ -1,75 +0,0 @@ - - - - -# module `setters` - - - - - ---- - - - -## function `get_time` - -```python -get_time(e, prop) -``` - - - - - - ---- - - - -## function `set_time` - -```python -set_time(e, prop, value) -``` - - - - - - ---- - - - -## function `get_text` - -```python -get_text(tag) -``` - - - - - - ---- - - - -## function `set_text` - -```python -set_text(tag, value) -``` - - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/version.md b/version.md deleted file mode 100644 index c6025232..00000000 --- a/version.md +++ /dev/null @@ -1,16 +0,0 @@ - - - - -# module `version` - - - - - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._ diff --git a/xpath.md b/xpath.md deleted file mode 100644 index 784559d0..00000000 --- a/xpath.md +++ /dev/null @@ -1,22 +0,0 @@ - - - - -# module `xpath` - - - - -**Global Variables** ---------------- -- **attachment_xp** -- **path_xp** -- **entry_xp** -- **group_xp** - - - - ---- - -_This file was automatically generated via [lazydocs](https://github.com/ml-tooling/lazydocs)._