commit
cb97bfb387
3 changed files with 309 additions and 0 deletions
27
homework05/Makefile
Normal file
27
homework05/Makefile
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
test:
|
||||
@$(MAKE) -sk test-all
|
||||
|
||||
test-all: test-searx test-hulk
|
||||
|
||||
test-data:
|
||||
@curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework05/hulk.hashes
|
||||
@echo "*.hashes" > .gitignore
|
||||
@echo "*.test" >> .gitignore
|
||||
|
||||
test-scripts: test-data
|
||||
@curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework05/searx.test
|
||||
@curl -sLO https://www3.nd.edu/~pbui/teaching/cse.20289.sp25/static/txt/homework05/hulk.test
|
||||
@chmod +x ./*.test
|
||||
|
||||
test-searx: test-scripts searx.py
|
||||
@echo Testing SearX ...
|
||||
@./searx.test -v
|
||||
@echo
|
||||
|
||||
test-hulk: test-scripts hulk.py
|
||||
@echo Testing Hulk ...
|
||||
@./hulk.test -v
|
||||
@echo
|
||||
|
||||
clean:
|
||||
@rm -f *.test *.hashes
|
||||
173
homework05/hulk.py
Executable file
173
homework05/hulk.py
Executable file
|
|
@ -0,0 +1,173 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from typing import Iterable, Iterator
|
||||
|
||||
import concurrent.futures
|
||||
import hashlib
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
|
||||
# Constants
|
||||
|
||||
ALPHABET = string.ascii_lowercase + string.digits
|
||||
|
||||
# Functions
|
||||
|
||||
def usage(exit_code: int=0):
|
||||
print('''Usage: hulk.py [-a ALPHABET -c CORES -l LENGTH -p PATH -s HASHES]
|
||||
-a ALPHABET Alphabet to use in permutations
|
||||
-c CORES CPU Cores to use
|
||||
-l LENGTH Length of permutations
|
||||
-p PREFIX Prefix for all permutations
|
||||
-s HASHES Path of hashes file''', file=sys.stderr)
|
||||
sys.exit(exit_code)
|
||||
|
||||
def sha1sum(s: str) -> str:
|
||||
''' Compute SHA1 digest for given string.
|
||||
|
||||
>>> sha1sum('a')
|
||||
'86f7e437faa5a7fce15d1ddcb9eaeaea377667b8'
|
||||
'''
|
||||
hashOfString = hashlib.sha1()
|
||||
hashOfString.update(s.encode('utf-8'))
|
||||
return hashOfString.hexdigest()
|
||||
|
||||
def permutations(length: int, alphabet: str=ALPHABET) -> Iterator[str]:
|
||||
''' Recursively yield all permutations of alphabet up to given length.
|
||||
|
||||
>>> for p in permutations(2, 'ab'): print(p)
|
||||
aa
|
||||
ab
|
||||
ba
|
||||
bb
|
||||
'''
|
||||
if length == 0: # Base case
|
||||
yield ''
|
||||
else: # Recursive case
|
||||
for prefix in alphabet:
|
||||
for suffix in permutations(length - 1, alphabet):
|
||||
yield prefix + suffix # this can be done in one line but I think this is more readble
|
||||
|
||||
def flatten(sequence: Iterable[Iterable[str]]) -> Iterator[str]:
|
||||
''' Flatten sequence of iterables.
|
||||
|
||||
>>> for p in flatten([['a', 'b'], ['c', 'd']]): print(p)
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
'''
|
||||
for sub_iterable in sequence:
|
||||
yield from sub_iterable
|
||||
|
||||
|
||||
|
||||
def crack(hashes: set[str], length: int, alphabet: str=ALPHABET, prefix: str='') -> list[str]:
|
||||
''' Return all password permutations of specified length that are in hashes
|
||||
by trying all possible permutations sequentially.
|
||||
|
||||
>>> for p in crack({sha1sum(l) for l in 'abc'}, 1, 'abcd'): print(p)
|
||||
a
|
||||
b
|
||||
c
|
||||
'''
|
||||
matches = []
|
||||
for perm in permutations(length, alphabet):
|
||||
candidate = prefix + perm
|
||||
if sha1sum(candidate) in hashes:
|
||||
matches.append(candidate)
|
||||
return matches
|
||||
|
||||
|
||||
|
||||
def whack(arguments: tuple[set[str], int, str, str]) -> list[str]:
|
||||
''' Call the crack function with the specified list of arguments
|
||||
|
||||
>>> for p in whack([{sha1sum(l) for l in 'abc'}, 1, 'abcd', '']): print(p)
|
||||
a
|
||||
b
|
||||
c
|
||||
'''
|
||||
hashes, length, alphabet, prefix = arguments
|
||||
return crack(hashes, length, alphabet, prefix)
|
||||
|
||||
def smash(hashes: set[str], length: int, alphabet: str=ALPHABET, prefix: str='', cores: int=1) -> Iterator[str]:
|
||||
''' Return all password permutations of specified length that are in hashes
|
||||
by cracking subsets of all possible permutations concurrently.
|
||||
|
||||
>>> for p in smash({sha1sum(l) for l in 'abc'}, 1, 'abcd'): print(p)
|
||||
a
|
||||
b
|
||||
c
|
||||
'''
|
||||
arguments = ((hashes, length-1, alphabet, prefix + p) for p in alphabet)
|
||||
|
||||
# Use the ProcessPoolExecutor to make use of mutliple cores (specified by user)
|
||||
with concurrent.futures.ProcessPoolExecutor(cores) as executor:
|
||||
results = executor.map(whack, arguments)
|
||||
|
||||
return flatten(results) # return results after flattening them
|
||||
|
||||
|
||||
|
||||
# Main Execution
|
||||
|
||||
def main(arguments: list[str]=sys.argv[1:]) -> None:
|
||||
''' Smashes given hashes to determine passwords with specified alphabet,
|
||||
length, and prefix. Uses multiple cores (ie. processes) if specified.
|
||||
|
||||
>>> main('-a abcdefg -l 2'.split())
|
||||
cg
|
||||
fg
|
||||
gg
|
||||
'''
|
||||
alphabet = ALPHABET
|
||||
cores = 1
|
||||
hashes_path = 'hulk.hashes'
|
||||
length = 1
|
||||
prefix = ''
|
||||
|
||||
# Parse command line arguments (I use the same while loop structure as searx.py)
|
||||
i = 0
|
||||
while i < len(arguments):
|
||||
arg = arguments[i]
|
||||
|
||||
if arg == '-a':
|
||||
alphabet = arguments[i+1]
|
||||
i += 2
|
||||
elif arg == '-c':
|
||||
cores = int(arguments[i+1])
|
||||
i += 2
|
||||
elif arg == '-l':
|
||||
length = int(arguments[i+1])
|
||||
i += 2
|
||||
elif arg == '-p':
|
||||
prefix = arguments[i+1]
|
||||
i += 2
|
||||
elif arg == '-s':
|
||||
hashes_path = arguments[i+1]
|
||||
i += 2
|
||||
elif arg == '-h':
|
||||
usage(0)
|
||||
else:
|
||||
usage(1)
|
||||
|
||||
# Load hashes set
|
||||
hashes = set()
|
||||
with open(hashes_path) as hashfile:
|
||||
for line in hashfile:
|
||||
hashes.add(line.strip())
|
||||
|
||||
# Execute smash function and store results
|
||||
results = smash(hashes, length, alphabet, prefix, cores)
|
||||
|
||||
# Print all found passwords
|
||||
for password in results:
|
||||
print(password)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
# vim: set sts=4 sw=4 ts=8 expandtab ft=python:
|
||||
109
homework05/searx.py
Executable file
109
homework05/searx.py
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
''' searx.py - SearX from the command line '''
|
||||
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
# Constants
|
||||
|
||||
URL = 'https://searx.ndlug.org/search'
|
||||
LIMIT = 5
|
||||
ORDERBY = 'score'
|
||||
|
||||
# Functions
|
||||
|
||||
def usage(exit_status: int=0) -> None:
|
||||
''' Print usage message and exit. '''
|
||||
print(f'''Usage: searx.py [-u URL -n LIMIT -o ORDERBY] QUERY
|
||||
|
||||
Fetch SearX results for QUERY and print them out.
|
||||
|
||||
-u URL Use URL as the SearX instance (default is: {URL})
|
||||
-n LIMIT Only display up to LIMIT results (default is: {LIMIT})
|
||||
-o ORDERBY Sort the search results by ORDERBY (default is: {ORDERBY})
|
||||
|
||||
If ORDERBY is score, the results are shown in descending order. Otherwise,
|
||||
results are shown in ascending order.''', file=sys.stderr)
|
||||
sys.exit(exit_status)
|
||||
|
||||
def searx_query(query: str, url: str=URL) -> list[dict]:
|
||||
''' Returns lists of results for query from SearX.
|
||||
|
||||
>>> searx_query('Python', 'https://yld.me/iB1T') # doctest: +ELLIPSIS
|
||||
[{'url': 'https://www.python.org/', 'title': 'Welcome to Python.org', ...}]
|
||||
'''
|
||||
|
||||
parameters = {'q': query, 'format': 'json'}
|
||||
response = requests.get(url, params=parameters)
|
||||
return response.json()['results']
|
||||
|
||||
def print_results(results: list[dict], limit: int=LIMIT, orderby: str=ORDERBY) -> None:
|
||||
''' Print results of SearX query.
|
||||
|
||||
>>> print_results(searx_query('Python', 'https://yld.me/iB1T')) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
1. Welcome to Python.org [...]
|
||||
https://www.python.org/
|
||||
...
|
||||
'''
|
||||
|
||||
sorted_results = sorted(results, key=lambda x: x[orderby], reverse=(orderby=='score'))
|
||||
|
||||
for index, result in enumerate(sorted_results[:limit], 1):
|
||||
print(f"{index:>4}.\t{result['title']} [{result['score']:0.2f}]")
|
||||
print(f"\t{result['url']}")
|
||||
if index < len(sorted_results[:limit]): # only prints new line between items
|
||||
print()
|
||||
|
||||
# Main Execution
|
||||
|
||||
def main(arguments=sys.argv[1:]) -> None:
|
||||
''' Searches SearX and print results.
|
||||
|
||||
>>> main('-u https://yld.me/iB1T Python'.split()) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
|
||||
1. Welcome to Python.org [...]
|
||||
https://www.python.org/
|
||||
...
|
||||
'''
|
||||
|
||||
# Set variables equal to the constants (initially)
|
||||
url = URL
|
||||
limit = LIMIT
|
||||
orderby = ORDERBY
|
||||
|
||||
search_terms = []
|
||||
|
||||
i = 0
|
||||
while i < len(arguments):
|
||||
arg = arguments[i]
|
||||
|
||||
if arg == '-u':
|
||||
url = arguments[i+1]
|
||||
i += 2
|
||||
elif arg == '-n':
|
||||
limit = int(arguments[i+1]) # convert the argument to int before setting limit
|
||||
i += 2
|
||||
elif arg == '-o':
|
||||
orderby = arguments[i+1]
|
||||
i += 2
|
||||
elif arg == '-h':
|
||||
usage(0)
|
||||
elif arg.startswith('-'): # handles case where user enters an invalid command
|
||||
usage(1)
|
||||
else: # if it's not a command and it's not invalid, I can assume it's a search term
|
||||
search_terms.append(arg)
|
||||
i += 1
|
||||
|
||||
# Display the usage message if the user didn't enter a search term
|
||||
if not search_terms:
|
||||
usage(1)
|
||||
|
||||
query = ' '.join(search_terms)
|
||||
results = searx_query(query, url)
|
||||
print_results(results, limit, orderby)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
# vim: set sts=4 sw=4 ts=8 expandtab ft=python:
|
||||
Loading…
Reference in a new issue