diff --git a/kdbxpasswordpwned.py b/kdbxpasswordpwned.py index fcd1ed9..8939b04 100755 --- a/kdbxpasswordpwned.py +++ b/kdbxpasswordpwned.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import libkeepass +import pykeepass import requests import hashlib import argparse @@ -32,19 +32,17 @@ def check_hash(password): def main(args=None): opt = build_parser().parse_args(args) - with libkeepass.open(opt.kdbx, password=getpass.getpass(), keyfile=opt.keyfile, mode='rb') as kdb: - for entry in kdb.obj_root.findall('.//Group/Entry'): - uuid = entry.find('./UUID').text - kv = {string.find('./Key').text: string.find('./Value').text for string in entry.findall('./String')} - if not kv['Password']: + with pykeepass.PyKeePass(opt.kdbx, password=getpass.getpass(), keyfile=opt.keyfile) as kdb: + for entry in kdb.entries: + if not entry.password: continue - r = check_hash(kv['Password']) + r = check_hash(entry.password) if r > 0: - m = 'Password for %s (%s) seen %d times before' % (kv['Title'], uuid, r) + m = 'Password for %s seen %d times before' % (entry.title, r) if opt.show_user: - m += ' - %s' % kv.get('UserName') + m += ' - %s' % entry.username if opt.show_password: - m += ' - %s' % kv['Password'] + m += ' - %s' % entry.password print(m) diff --git a/requirements.txt b/requirements.txt index a465da6..b83655c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ libkeepass==0.3.0 requests==2.20.1 +pykeepass==3.0.2 \ No newline at end of file diff --git a/setup.py b/setup.py index ce7c277..b8d28ac 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ py_modules=['kdbxpasswordpwned'], install_requires=[ 'requests', - 'libkeepass', + 'pykeepass==3.0.2', ], entry_points={ 'console_scripts': ['kdbxpasswordpwned=kdbxpasswordpwned:main'] diff --git a/tests.py b/tests.py index 22ac6b0..fa60ac9 100644 --- a/tests.py +++ b/tests.py @@ -6,6 +6,7 @@ from contextlib import contextmanager import sys import os +import construct try: from cStringIO import StringIO except ImportError: @@ -61,8 +62,8 @@ def test_check_hash(self, req_mock): def test_wrong_password(self, gp_mock): gp_mock.return_value = 'wrong' self.assertRaisesRegexp( - IOError, - 'Master key invalid.', + construct.ChecksumError, + "wrong checksum, read b{0,1}'.+?', computed b{0,1}'.+?'", kdbxpasswordpwned.main, [_asset('sample.kdbx')] ) @@ -79,8 +80,8 @@ def test_run(self, ch_mock, gp_mock): self.assertEqual( fout[0].getvalue(), '''\ -Password for title1 (FEiAje5y9FQmdVCSFDuSRA==) seen 3 times before -Password for title2 (c3NVlIIN/pPhrM9Pk4Ow+Q==) seen 3 times before +Password for title1 seen 3 times before +Password for title2 seen 3 times before ''' ) ch_mock.assert_has_calls([ @@ -101,8 +102,8 @@ def test_run_show_user_and_password(self, ch_mock, gp_mock): self.assertEqual( fout[0].getvalue(), '''\ -Password for title1 (FEiAje5y9FQmdVCSFDuSRA==) seen 2 times before - testuser - testit -Password for title2 (c3NVlIIN/pPhrM9Pk4Ow+Q==) seen 2 times before - None - blabla +Password for title1 seen 2 times before - testuser - testit +Password for title2 seen 2 times before - None - blabla ''' ) ch_mock.assert_has_calls([ @@ -114,8 +115,8 @@ def test_run_show_user_and_password(self, ch_mock, gp_mock): def test_run_keyfile_missing(self, gp_mock): gp_mock.return_value = 'reallysafeone' self.assertRaisesRegexp( - IOError, - 'Master key invalid.', + construct.ChecksumError, + "wrong checksum, read b{0,1}'.+?', computed b{0,1}'.+?'", kdbxpasswordpwned.main, [_asset('sample_with_key.kdbx')] )