Compare commits

..

No commits in common. "master" and "beta" have entirely different histories.
master ... beta

4 changed files with 38 additions and 110 deletions

View File

@ -8,7 +8,6 @@ verify_ssl = true
[packages] [packages]
requests = "*" requests = "*"
pyinstaller = "*" pyinstaller = "*"
argparse = "*"
[requires] [requires]
python_version = "3.7" python_version = "3.7"

11
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "72f7b87a032a3c10cfa8b0914121403db30a4f3582386cce2f516a1b7f897b42" "sha256": "38e2af2d59158d85bb4d55eb3140a0745c9eb21635643df4b2192cdd0abc2074"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -23,14 +23,6 @@
], ],
"version": "==0.16.1" "version": "==0.16.1"
}, },
"argparse": {
"hashes": [
"sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4",
"sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314"
],
"index": "pypi",
"version": "==1.4.0"
},
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5",
@ -47,6 +39,7 @@
}, },
"future": { "future": {
"hashes": [ "hashes": [
"sha256:1d73b8a1aab19cb8c2c961ba82bf93860e1fb7d361be21e7288691c068cd3cfc",
"sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8" "sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8"
], ],
"version": "==0.17.1" "version": "==0.17.1"

2
build.sh Executable file → Normal file
View File

@ -2,4 +2,4 @@
mkdir -p build mkdir -p build
cd build cd build
pyinstaller --distpath=../dist --workpath=../build -y ../src/have_I_b33n_pwned.py pyinstaller --distpath=../dist --workpath=../build ../src/have_I_b33n_pwned.py

134
src/have_I_b33n_pwned.py Executable file → Normal file
View File

@ -21,83 +21,44 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# updater script for nextcloud # updater script for nextcloud
# #
#TODO # TODO
# - add feature: also check for email breaches
# - add feature: keepass integration? isnt there something like this already? # - add feature: keepass integration? isnt there something like this already?
# - handle mail API exeption more precisely
from os import name
from sys import argv, stdout, exit from sys import argv, stdout, exit
from re import match
from argparse import ArgumentParser
from hashlib import sha1 from hashlib import sha1
from getpass import getpass from getpass import getpass
import requests from requests import get
RED = "\033[1;31m"
GREEN = "\033[0;32m"
RESET = "\033[0;0m"
API = 'https://api.pwnedpasswords.com/range/'
ROW = '{:<30}{:<10}{:<45}'
HIDDEN = False
if name == 'nt': # disable colors for windows environment def header():
(RED, GREEN, RESET) = ("", "", "") print()
else: print(ROW.format('password', 'leaked', 'sha1'))
(RED, GREEN, RESET) = ("\033[1;31m", "\033[0;32m", "\033[0;0m") print('-' * 80)
PASSWORD_API = 'https://api.pwnedpasswords.com/range/'
MAIL_API = 'https://haveibeenpwned.com/api/v2/breachedaccount/'
ROW = '{:<30}{:<10}{:<45}'
HIDDEN = True
INFINITE = True
parser = ArgumentParser( def prompt_password():
description='Check if your email or password appears in a data leak.', print()
epilog='Moritz Münch, moritzmuench@mailbox.org, willipink.eu, GPL3+') password = getpass('Tell me your password: ')
parser.add_argument('input',
nargs='*',
metavar='password or mail adress',
help='''You can specify passwords and mail adresses seperated by spaces.
If the password contains a space you have to enclose it in quotes like ' or
"''')
parser.add_argument('-s', '-show', '--show',
nargs='?',
help='Show entered in leak summary')
args = parser.parse_args()
if args.show:
HIDDEN = False
def input_prompt():
global HIDDEN global HIDDEN
if HIDDEN: HIDDEN = True
user_input = getpass('Tell me your password or mail adress: ') header()
else: query(password)
user_input = input('Tell me your password or mail adress: ')
check_mail_or_password(user_input)
def check_mail_or_password(user_input): def query(password):
valid_mail = match(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', user_input)
if valid_mail:
query_mail(valid_mail.group())
else:
query_password(user_input)
def no_connection_to(url):
''' Warn about no connection to *url* and exit '''
stdout.write(RED)
print('Could not connect to {}, please check your internet connection'.format(url))
stdout.write(RESET)
exit(1)
def query_password(password):
''' Query the first 5 digits of the sha1 hash of the user input
ti the haveibeenpwned.com api. '''
password_hash = sha1(password.encode('UTF-8')).hexdigest().upper() password_hash = sha1(password.encode('UTF-8')).hexdigest().upper()
request = password_hash[:5] request = password_hash[:5]
try: response = get(API + request).text
response = requests.get(PASSWORD_API + request).text
except requests.exceptions.ConnectionError:
no_connection_to(PASSWORD_API)
hash_searched = 'not yet' hash_searched = 'not yet'
for answer in response.splitlines(): for answer in response.splitlines():
data = answer.split(':') data = answer.split(':')
@ -105,53 +66,28 @@ def query_password(password):
if password_hash == combined_hash: if password_hash == combined_hash:
hash_searched = int(data[1]) hash_searched = int(data[1])
break break
if hash_searched == 'not yet': if hash_searched == 'not yet':
stdout.write(GREEN) stdout.write(GREEN)
else: else:
stdout.write(RED) stdout.write(RED)
if HIDDEN: if HIDDEN:
password = '*' * len(password) password = '*' * len(password)
print(ROW.format('password', 'leaked', 'sha1'))
print('-' * 80)
print(ROW.format(password, hash_searched, password_hash)) print(ROW.format(password, hash_searched, password_hash))
stdout.write(RESET) stdout.write(RESET)
if INFINITE:
input_prompt()
if HIDDEN:
def query_mail(mail): prompt_password()
try:
response = requests.get(MAIL_API + mail).json()
for breach in response:
print('Name: {}'.format(breach['Name']))
print('Title: {}'.format(breach['Title']))
print('Domain: {}'.format(breach['Domain']))
print('Breach date: {}'.format(breach['BreachDate']))
print('Added date: {}'.format(breach['AddedDate']))
print('Modified date: {}'.format(breach['ModifiedDate']))
print('Pwn count: {}'.format(breach['PwnCount']))
print('Description: {}'.format(breach['Description']))
stdout.write(RED)
print('Breached data: {}'.format(', '.join([data for data in breach['DataClasses']])))
stdout.write(RESET)
print('Verified: {}'.format('yes' if breach['IsVerified'] == 1 else 'No'))
print('Fabricated: {}'.format('yes' if breach['IsFabricated'] == 1 else 'No'))
print('Sensitive: {}'.format('yes' if breach['IsSensitive'] == 1 else 'No'))
print('Retired: {}'.format('yes' if breach['IsRetired'] == 1 else 'No'))
print('Spam: {}'.format('yes' if breach['IsSpamList'] == 1 else 'No'))
except requests.exceptions.ConnectionError:
no_connection_to(MAIL_API)
except:
stdout.write(GREEN)
print('The mail adress {} is not in the leak database.'.format(mail))
stdout.write(RESET)
query_password(mail)
if __name__ == '__main__': if __name__ == '__main__':
if args.input: if len(argv) < 2:
for user_input in args.input: prompt_password()
check_mail_or_password(user_input)
else: else:
INFINITE = True header()
input_prompt() for password in argv[1:]:
query(password)
print()
exit(0)