Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initializing finished; porting to python v3; configuring package "keepass" #12

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# keepassc and python-keepass

This provides command line and Python interfaces for operating on
This provides command line and Python (both 2 and 3) interfaces for operating on
files in KeePass format v3 (used by [KeePass](http://keepass.info/)
1.x, and [KeePassX](http://www.keepassx.org/)). Note, this is not the
format used by the KeePass application version 2.x.
Expand Down
106 changes: 106 additions & 0 deletions README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# keepassc and python-keepass

This provides command line and Python (both 2 and 3) interfaces for operating on
files in KeePass format v3 (used by [KeePass](http://keepass.info/)
1.x, and [KeePassX](http://www.keepassx.org/)). Note, this is not the
format used by the KeePass application version 2.x.

## Notes of caution

Before using this code, understand the its (known) security
and correctness limitations:

* Unlike the KeePass/KeePassX GUI applications this code makes no
attempt to secure its memory. Input files read in are stored in
memory fully decrypted.

* It is quite easy to display the stored passwords in plain text,
although the defaults try to avoid this.

* Specifying the master key on the command line will leave traces in
your shells history and in the process list.

* While input files are treated as read-only, keep backups of any
files written by KeePass/KeePassX until you are assured that files
written by this code are usable.

* Key files are not currently supported.

## Prerequisites and Installation

You will need to install the python-crypto package (providing the
"Crypto" module). On a well behaved system do:

```shell
sudo apt-get install python-crypto
```

If installing into a [virtualenv](http://www.virtualenv.org) this prerequisite will be installed for you:

```shell
virtualenv /path/to/venv
source /path/to/venv/bin/activate
cd python-keepass
python setup.py install
```


## Command line

The command line interface is run like:

```shell
keepassc [general_options] [command command_options] ...
```

Multiple commands can be specified and will be executed in order.
They operate on an in-memory instance of the database file. An
example,

```shell
keepass open -m secret file.kdb \
dump -p -f '%(username)s password is: %(password)s' \
save -m newsecret backup.kdb
```

Online help:

```shell
keepass -h # short usage
keepass help # full usage
```

## Python Modules

### Low level file access

```python
from keepass import kpdb
db = kpdb.Database(filename,masterkey)
print db # warning: displayed passwords in plaintext!
```

# References and Credits

## PyCrypto help

* Main page is found through <http://pycrypto.org/>. The documentation there is a start, but not enough.
* This blog post is useful for the basics: <http://eli.thegreenplace.net/2010/06/25/aes-encryption-of-files-in-python-with-pycrypto/>

## The giants on whose shoulders this works stands

First, thanks to the original authors, contributors and community
behind KeePass and KeePassX. I am merely a user of KeePassX.

A big credit is due to rudi & shirou (same hacker?) for the following:

* <http://d.hatena.ne.jp/rudi/20101003/1286111011>
* <http://github.com/shirou/kptool>

Looking through KeePass/KeePassX source made my head swim. Only after
reviewing their work could I get started.

## License

This package is Free Software licensed to you under the GPL v2 or
later at your discretion. See the [LICENSE.txt](LICENSE.txt) file for details.
11 changes: 11 additions & 0 deletions python/keepass/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,14 @@

# ... even thoough this file is essentially empty....

__author__ = ["Brett Viren", "Jeremy Ehrhardt", "Filipp Frizzy"]
__copyright__ = ""
__credits__ = ["Brett Viren", "Jeremy Ehrhardt", "Filipp Frizzy"]
__license__ = "GPLv2+"
__version__ = "1.2"
__maintainer__ = "Filipp Frizzy"
__email__ = "[email protected]"
__status__ = "Development"
__description__ = "Python (both 2 and 3) interface and cli to KeePass file format v3 (used in KeePass V1.x and KeePassX)"
__keywords__ = "python keepass kdb"
__url__ = "https://github.com/Friz-zy/python-keepass"
29 changes: 13 additions & 16 deletions python/keepass/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
# later version.

import sys
import six
from optparse import OptionParser
import getpass
from keepass import kpdb

class Cli(object):
'''
Expand Down Expand Up @@ -49,7 +53,7 @@ def splitopts(argv):
cmd=""
if argv[0][0] != '-':
if argv[0] not in Cli.commands:
raise ValueError,'Unknown command: "%s"'%argv[0]
raise ValueError('Unknown command: "%s"'%argv[0])
cmd = argv.pop(0)
pass
copy = list(argv)
Expand Down Expand Up @@ -79,7 +83,7 @@ def splitopts(argv):
def __call__(self):
'Process commands'
if not self.command_line:
print self._general_op().print_help()
six.print_((self._general_op().print_help()))
return
for cmd,cmdopts in self.command_line:
meth = eval('self._%s'%cmd)
Expand All @@ -99,7 +103,6 @@ def _general_op(self):

execute "help" command for more information.
'''
from optparse import OptionParser
op = OptionParser(usage=self._general_op.__doc__)
return op

Expand All @@ -114,25 +117,24 @@ def _help_op(self):
def _help(self,opts):
'Print some helpful information'

print 'Available commands:'
six.print_(('Available commands:'))
for cmd in Cli.commands:
meth = eval('self._%s'%cmd)
print '\t%s: %s'%(cmd,meth.__doc__)
six.print_(('\t%s: %s'%(cmd,meth.__doc__)))
continue
print '\nPer-command help:\n'
six.print_(('\nPer-command help:\n'))

for cmd in Cli.commands:
meth = eval('self._%s_op'%cmd)
op = meth()
if not op: continue
print '%s'%cmd.upper()
six.print_(('%s'%cmd.upper()))
op.print_help()
print
six.print_(())
continue

def _open_op(self):
'open [options] filename'
from optparse import OptionParser
op = OptionParser(usage=self._open_op.__doc__,add_help_option=False)
op.add_option('-m','--masterkey',type='string',default="",
help='Set master key for decrypting file, default: ""')
Expand All @@ -141,20 +143,18 @@ def _open_op(self):
def _open(self,opts):
'Read a file to the in-memory database'
opts,files = self.ops['open'].parse_args(opts)
import kpdb
# fixme - add support for openning/merging multiple DBs!
try:
dbfile = files[0]
except IndexError:
print "No database file specified"
six.print_(("No database file specified"))
sys.exit(1)
self.db = kpdb.Database(files[0],opts.masterkey)
self.hier = self.db.hierarchy()
return

def _save_op(self):
'save [options] filename'
from optparse import OptionParser
op = OptionParser(usage=self._save_op.__doc__,add_help_option=False)
op.add_option('-m','--masterkey',type='string',default="",
help='Set master key for encrypting file, default: ""')
Expand All @@ -169,7 +169,6 @@ def _save(self,opts):

def _dump_op(self):
'dump [options] [name|/group/name]'
from optparse import OptionParser
op = OptionParser(usage=self._dump_op.__doc__,add_help_option=False)
op.add_option('-p','--show-passwords',action='store_true',default=False,
help='Show passwords as plain text')
Expand All @@ -184,13 +183,12 @@ def _dump(self,opts):
if not self.hier:
sys.stderr.write('Can not dump. No database open.\n')
return
print self.hier
six.print_((self.hier))
#self.hier.dump(opts.format,opts.show_passwords)
return

def _entry_op(self):
'entry [options] username [password]'
from optparse import OptionParser
op = OptionParser(usage=self._entry_op.__doc__,add_help_option=False)
op.add_option('-p','--path',type='string',default='/',
help='Set folder path in which to store this entry')
Expand All @@ -208,7 +206,6 @@ def _entry_op(self):

def _entry(self,opts):
'Add an entry into the database'
import getpass
opts,args = self.ops['entry'].parse_args(opts)
username = args[0]
try:
Expand Down
41 changes: 20 additions & 21 deletions python/keepass/header.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,26 @@
# later version.

import Crypto.Random
import struct
import six

class DBHDR(object):
'''
Interface to the database header chunk.
'''

format = [
('signature1',4,'I'),
('signature2',4,'I'),
('flags',4,'I'),
('version',4,'I'),
('master_seed',16,'16s'),
('encryption_iv',16,'16s'),
('ngroups',4,'I'),
('nentries',4,'I'),
('contents_hash',32,'32s'),
('master_seed2',32,'32s'),
('key_enc_rounds',4,'I'),
('signature1',4,b'I'),
('signature2',4,b'I'),
('flags',4,b'I'),
('version',4,b'I'),
('master_seed',16,b'16s'),
('encryption_iv',16,b'16s'),
('ngroups',4,b'I'),
('nentries',4,b'I'),
('contents_hash',32,b'32s'),
('master_seed2',32,b'32s'),
('key_enc_rounds',4,b'I'),
]

signatures = (0x9AA2D903,0xB54BFB65)
Expand All @@ -86,6 +88,9 @@ def __init__(self,buf=None):
self.version = 0x30002
self.flags = 3 # SHA2 hashing, AES encryption
self.key_enc_rounds = 50000
self.ngroups = 0
self.nentries = 0
self.contents_hash = ""
self.reset_random_fields()

def reset_random_fields(self):
Expand All @@ -112,29 +117,23 @@ def encryption_type(self):

def encode(self):
'Provide binary string representation'
import struct

ret = ""

ret = b""
for field in DBHDR.format:
name,bytes,typecode = field
value = self.__dict__[name]
buf = struct.pack('<'+typecode,value)
buf = struct.pack(b'<'+typecode,value)
ret += buf
continue
return ret

def decode(self,buf):
'Fill self from binary string.'
import struct

index = 0

for field in DBHDR.format:
name,nbytes,typecode = field
string = buf[index:index+nbytes]
index += nbytes
value = struct.unpack('<'+typecode, string)[0]
value = struct.unpack(b'<'+typecode, string)[0]
self.__dict__[name] = value
continue

Expand All @@ -143,7 +142,7 @@ def decode(self,buf):
msg = 'Bad sigs:\n%s %s\n%s %s'%\
(DBHDR.signatures[0],DBHDR.signatures[1],
self.signature1,self.signature2)
raise IOError,msg
raise IOError(msg)

return

Expand Down
Loading