Last active
May 18, 2020 08:28
-
-
Save flaf/11f3313ce8deaffc34c72a6862df7e1f to your computer and use it in GitHub Desktop.
python-and-multiprocessing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import string | |
import itertools | |
import hashlib | |
import json | |
from multiprocessing import Pool, Manager | |
U = string.ascii_uppercase # ['A', 'B', ..., 'Z'] | |
L = string.ascii_lowercase # ['a', 'b', ..., 'z'] | |
D = string.digits # [0, 1, ..., 9] | |
C = ['#','!','?','*','%'] | |
# Le point de départ est que j'ai un fichier JSON de cette forme là : | |
# | |
# { | |
# 'c17c15bc2e54dfe3b6f7b25f084cd25fec662b16': 'username1', | |
# '6187b6bc8a68cb1330251f427603de32900443d7': 'username2', | |
# '374582699bb845bd8cc3984627118af56ba7ab98': 'username3', | |
# ... | |
# } | |
# | |
# Il contient environ 600 000 entrées. Une clé est simplement le sha1sum du | |
# mot de passe d'un utilisateur, la valeur est le login d'un utilisateur. | |
# | |
# Postulat: on sait que tous les mots de passe sont de la forme : | |
# | |
# <u1><u1><d1><d2><l1><l2><d3><c1> avec u1, u2 dans U | |
# d1, d2, d3 dans D | |
# l1, l2 dans L | |
# et c1 dans C | |
# | |
# Comme par exemple: AB34xy5* | |
# | |
# Au total, ça fait un peu plus de 2 milliards de combinaisons possibles pour | |
# les mots de passe. Mon ordinateur dispose de 8 coeurs de CPU. Le but du | |
# programme est simplement calculer le sha1sum de ces 2 milliards de mots de | |
# passe afin de retrouver le mot de passe de chacun des utilisateurs du | |
# fichier JSON. | |
# | |
# Comme il y a 2 milliards de sha1sum à calculer, il faut tenter de | |
# paralléliser le mieux possible avec le multiprocessing. On va découper les | |
# calculs des sha1sum en plusieurs « paquets » disjoints pour les workers. | |
# Il y aura : | |
# | |
# - le worker « 0 » qui va calculer tous les sha1sum des mots de passe de la forme <u1><u1><d1><d2><l1><l2>0<c1> | |
# - le worker « 1 » qui va calculer tous les sha1sum des mots de passe de la forme <u1><u1><d1><d2><l1><l2>1<c1> | |
# - ... | |
# - le worker « 9 » qui va calculer tous les sha1sum des mots de passe de la forme <u1><u1><d1><d2><l1><l2>9<c1> | |
# | |
# Ça fera 10 workers pour mes 8 coeurs de CPU. J'aurais préféré 8 workers pour | |
# mes 8 coeurs (pour avoir 1 worker <=> 1 coeur de CPU) mais j'ai voulu faire | |
# un découpage simple alors tant pis. | |
# On récupère donc le fameux fichiers JSON que l'on charge sous la forme d'un | |
# dictionnaire Python. | |
with open('sha1sum_users.json', 'r') as f: | |
sha1sum_users = json.load(f) | |
# Et là, j'ai voulu faire un truc propre et classe. J'ai voulu que mes workers | |
# se partagent : | |
# | |
# 1. Le dictionnaire sha1sum_users pour que, dès qu'un worker calcule un | |
# sha1sum qu'il trouve dans ce dictionnaire, il supprime l'entrée dans ce | |
# dictionnaire. De cette manière, au fur et à mesure que les workers | |
# trouvent des mots de passe d'utilisateurs, le dictionnaire sha1sum_users | |
# se vide. | |
# | |
# 2. Un dictionnaire USERS_PASSWORDS, initialement vide, qui contiendrait des | |
# entrées de la forme: | |
# | |
# "username": "mot-de-passe-en-clair" | |
# | |
# Comme ça, dès qu'un worker trouve un mot de passe d'un utilisateur, | |
# il remplisse ce dictionnaire. Comme ça, à la fin de l'exécution des | |
# workers, on récupère un dictionnaire tout propre avec tous les | |
# identifiants. | |
# | |
# Dans le code, cela aurait donné quelque chose comme ça : | |
# | |
# manager = Manager() | |
# SHA1SUM_USERS = manager.dict(sha1sum_users) | |
# USERS_PASSWORDS = manager.dict() | |
# | |
# Et dans le code des workers, on aurait mis à jour ces deux dictionnaires. | |
# Seulement voilà, apparemment ça plombe les performances (ou alors je m'y | |
# prends mal). Il doit y avoir des locks sur ces dictionnaires posés par | |
# Python et cela engendre de la communication inter-processus qui ralentit | |
# tout. Au lieu d'avoir des CPU à 100% avec des workers qui calculent des | |
# sha1sum à tour de bras, j'ai des CPU à 20% environ, probablement en pause à | |
# cause des locks sur les dictionnaires et des communications entre les | |
# workers et le processus principal. C'est dommage, d'autant plus que 99.9% du | |
# temps les workers ne font que de l'accès en lecture sur ces objets | |
# dictionnaires (voir ne font rien du tout). Mais j'ai l'impression que, | |
# même en simple lecture, tout est ralentit malgré tout (ou alors, encore | |
# une fois, je m'y suis mal pris). | |
# | |
# Du coup, je laisse tomber le manager (snif), chaque worker va travailler | |
# tout seul dans son coin de manière indépendante et je vais devoir mon | |
# contenter de « print » dégueulasses dès qu'un mot de passe est trouvé. | |
# C'est naze mais je n'ai pas mieux pour l'instant. | |
# L'alternative avec un manager est donc abandonnée pour l'instant. On | |
# commente. | |
# | |
#manager = Manager() | |
#SHA1SUM_USERS = manager.dict(sha1sum_users) | |
#USERS_PASSWORDS = manager.dict() | |
def worker(d3): | |
for (u1, u2, d1, d2, l1, l2, c1) in itertools.product(U, U, D, D, L, L, C): | |
pwd = f'{u1}{u2}{d1}{d2}{l1}{l2}{d3}{c1}' | |
sha1 = hashlib.sha1(str.encode(pwd)).hexdigest() | |
try: | |
user = sha1sum_users[sha1] | |
# Pas d'exception KeyError, ça veut dire qu'on a trouvé un mot de | |
# passe d'un utilisateur \o/. On peut donc faire une « print » | |
# tout pourri. | |
print(f'"{user}": "{pwd}"') | |
# Dans un monde idéal avec notre manager, on mettrait à jour nos | |
# deux dictionnaires partagés. | |
# | |
#USERS_PASSWORDS[user] = pwd # <= ajout du login/password | |
#del SHA1SUM_USERS[sha1] # <= suppression de la clé sha1 | |
except KeyError: | |
# On a eu une exception, le sha1 calculé n'est pas présent dans le | |
# JSON. On passe au prochain calcul de sha1. | |
continue | |
pool = Pool() | |
for d3 in D: | |
pool.apply_async(worker, args=(d3,)) | |
pool.close() | |
pool.join() | |
# Idéalement avec notre manager, on aurait pu récupérer notre dictionnaire | |
# USERS_PASSWORDS avec tous les couples login/password. Mais cette option | |
# n'a pas été retenue faute de performance. | |
# | |
#with open('result.json', 'w') as f: | |
# json.dump(USERS_PASSWORDS, f, indent=4) | |
# | |
# On se contentera des « print » tout pourris des workers. | |
# | |
# Pour 60691 entrées dans le fichier JSON sha1sum_users.json, | |
# avec 8 coeurs de CPU (processeur Intel(R) Xeon(R) W-2125 CPU 4.00GHz), | |
# le programme s'est exécuté pendant environ 9 minutes. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment