Skip to content

Commit a94d920

Browse files
committed
Initial commit
1 parent fb18c00 commit a94d920

File tree

5 files changed

+290
-1
lines changed

5 files changed

+290
-1
lines changed

.gitignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
._*
2+
.~lock.*
3+
.buildpath
4+
.DS_Store
5+
.idea
6+
.project
7+
.settings
8+
.env
9+
__pycache__
10+
*.egg-info
11+
dist
12+
build
13+
*.pyc
14+
*.org
15+
*.old
16+
*.bak
17+
*.kdev4
18+
*~
19+
*.local
20+
.eggs
21+
.tox
22+
nosetests.xml
23+
.coverage
24+
coverage.xml
25+
cover
26+
config.ini
27+
.csync_*
28+
.owncloud*
29+
*.iml
30+
.vscode

README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,38 @@
11
# haveibeenpwned-query
2-
Simple query to Have I been Pwned API
2+
Simple query to Have I been Pwned API.
3+
4+
#### Installation
5+
6+
##### Installation from release :
7+
- Download and extract the latest [release](https://github.com/Danamir/haveibeenpwned-query/releases).
8+
- Open a terminal to the extracted directory.
9+
10+
##### Installation from sources :
11+
```shell
12+
$ curl --location https://github.com/Danamir/haveibeenpwned-query/archive/master.zip --output haveibeenpwned-query-master.zip
13+
$ unzip haveibeenpwned-query-master.zip
14+
$ mv haveibeenpwned-query-master/ haveibeenpwned-query
15+
$ cd haveibeenpwned-query
16+
```
17+
18+
##### Setup :
19+
_(Optional)_ Configure Python virtual environment :
20+
```shell
21+
$ python -m venv .env
22+
$ . .env/bin/activate (Linux)
23+
-or-
24+
$ .env/Script/activate.bat (Windows)
25+
```
26+
27+
Install :
28+
```shell
29+
$ pip install -r requirements.txt
30+
```
31+
32+
#### Running
33+
34+
Display help :
35+
```shell
36+
$ python pwned-account.py --help
37+
$ python pwned-password.py --help
38+
```

pwned-account.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!python
2+
"""Have I been Pwned account database check.
3+
4+
Usage: pwned-account [--help] [options] [ACCOUNT]
5+
6+
Arguments:
7+
ACCOUNT The account to check, if empty prompt for account.
8+
9+
Options:
10+
-d --debug Debug only (displays informations and quits).
11+
-h --help Displays the help.
12+
-v --verbose Displays more informations.
13+
"""
14+
15+
import sys
16+
import hashlib
17+
import json
18+
import re
19+
20+
import requests
21+
import docopt as docpt
22+
23+
from docopt import docopt
24+
25+
# options
26+
options = None # type: dict
27+
debug = False # type: bool
28+
verbose = False # type: bool
29+
30+
# arguments
31+
account = "" # type: str
32+
33+
34+
def main():
35+
"""Main method."""
36+
37+
# options
38+
global debug, verbose
39+
debug, verbose = options['--debug'], options['--verbose'] # type: bool, bool
40+
41+
if debug:
42+
verbose = True
43+
44+
# arguments
45+
global account
46+
account = options['ACCOUNT'] # type: str
47+
48+
if not account:
49+
account = input('Account: ')
50+
print()
51+
52+
if not account:
53+
raise RuntimeWarning('Empty account.')
54+
55+
# API URL
56+
url = 'https://haveibeenpwned.com/api/v2/breachedaccount/%s' % account
57+
58+
# verbose
59+
if verbose:
60+
print('API URL %s' % url)
61+
print()
62+
63+
# fetch matching hashes
64+
r = requests.get(url)
65+
66+
pwns = json.loads(r.content)
67+
68+
if debug:
69+
print(json.dumps(pwns, indent=4))
70+
71+
# output
72+
if not pwns:
73+
print('Account not pwned.')
74+
else:
75+
print('Pwned in %d breach%s:' % (len(pwns), 'es' if len(pwns) > 1 else ''))
76+
idx = 0
77+
for pwned in pwns: # type: dict
78+
idx = idx + 1
79+
label = pwned.get('Title', None)
80+
81+
if verbose:
82+
desc = pwned.get('Description', '')
83+
desc = re.sub(r'<a.*>(.*)</a>', r'\g<1>', desc)
84+
85+
label = '%s: %s' % (label, desc)
86+
label = '%s\n [%s]' % (label, ', '.join(pwned.get('DataClasses', [])))
87+
88+
print('%5d. %s' % (idx, label))
89+
90+
if verbose:
91+
print()
92+
print()
93+
94+
95+
def cli():
96+
"""Command-line interface"""
97+
global options
98+
options = docopt(__doc__)
99+
try:
100+
main()
101+
except RuntimeWarning as w:
102+
print(" Warning: %s" % w, file=sys.stderr)
103+
sys.exit(1)
104+
except RuntimeError as w:
105+
print("%s" % w, file=sys.stderr)
106+
print(docpt.printable_usage(__doc__))
107+
sys.exit(1)
108+
109+
110+
# main entry point
111+
if __name__ == '__main__':
112+
cli()

pwned-password.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!python
2+
"""Have I been Pwned password database check.
3+
4+
Usage: pwned-password [--help] [options] [PASSWORD]
5+
6+
Arguments:
7+
PASSWORD The password to check, if empty prompt for password.
8+
9+
Options:
10+
-d --debug Debug only (displays informations and quits).
11+
-h --help Displays the help.
12+
-v --verbose Displays more informations.
13+
"""
14+
15+
import sys
16+
import hashlib
17+
from getpass import getpass
18+
19+
import requests
20+
import docopt as docpt
21+
22+
from docopt import docopt
23+
24+
# options
25+
options = None # type: dict
26+
debug = False # type: bool
27+
verbose = False # type: bool
28+
29+
# arguments
30+
password = "" # type: str
31+
32+
33+
def main():
34+
"""Main method."""
35+
36+
# options
37+
global debug, verbose
38+
debug, verbose = options['--debug'], options['--verbose'] # type: bool, bool
39+
40+
if debug:
41+
verbose = True
42+
43+
# arguments
44+
global password
45+
password = options['PASSWORD'] # type: str
46+
47+
if not password:
48+
password = getpass('Password: ')
49+
print()
50+
51+
if not password:
52+
raise RuntimeWarning('Empty password.')
53+
54+
# SHA-1 hash password
55+
password_hash = hashlib.sha1(password.encode()).hexdigest().upper()
56+
password = ''
57+
58+
password_hash_prefix = password_hash[:5]
59+
password_hash_suffix = password_hash[5:]
60+
61+
# API URL
62+
url = 'https://api.pwnedpasswords.com/range/%s' % password_hash_prefix
63+
64+
# verbose
65+
if verbose:
66+
print('Password hash split: %s %s' % (password_hash_prefix, password_hash_suffix))
67+
print('API URL %s' % url)
68+
print()
69+
70+
# fetch matching hashes
71+
r = requests.get(url)
72+
73+
pwned_hashes = {}
74+
for pwned_hash in r.content.decode().split('\r\n'):
75+
if debug:
76+
print(pwned_hash)
77+
78+
h, n = pwned_hash.split(':')
79+
pwned_hashes[h] = n
80+
81+
# look for hash suffix
82+
pwned = pwned_hashes.get(password_hash_suffix, None)
83+
84+
# output
85+
if pwned is None:
86+
print('Password not pwned.')
87+
else:
88+
print('Password pwned, %s occurences.' % pwned)
89+
print()
90+
91+
92+
def cli():
93+
"""Command-line interface"""
94+
global options
95+
options = docopt(__doc__)
96+
try:
97+
main()
98+
except RuntimeWarning as w:
99+
print(" Warning: %s" % w, file=sys.stderr)
100+
sys.exit(1)
101+
except RuntimeError as w:
102+
print("%s" % w, file=sys.stderr)
103+
print(docpt.printable_usage(__doc__))
104+
sys.exit(1)
105+
106+
107+
# main entry point
108+
if __name__ == '__main__':
109+
cli()

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests
2+
docopt

0 commit comments

Comments
 (0)