Générateur de Brute Force
Je viens d'écrire un petit programme Python 3 pour cracher toutes les combinaisons possibles d'un ensemble de 99 caractères. Il fait le travail, mais je serais très intéressé par ce que vous en pensez.
Je suis juste quelques jours dans Python, donc je serais reconnaissant pour des conseils même apparemment évidents.
import sys
# List of 99 characters and a blank string:
lib=["","0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","°","!","\"","§","$","%","&","/","(",")","=","ß","´","`","+","#","-",".",",",">","<","@","€","|","^","~","–","{","[","]","}","Ä","Ö","Ü","ä","ö","ü"]
# 10 counters for up to 10-digit-combinations:
counter0=-1
counter1=0
counter2=0
counter3=0
counter4=0
counter5=0
counter6=0
counter7=0
counter8=0
counter9=0
# Repetitive if-statements adding to the counters:
for i in range(sys.maxsize**99999):
counter0+=1
if counter0>99:
counter0=counter0*0
counter1+=1
elif counter1>99:
counter1=counter1*0
counter2+=1
elif counter2>99:
counter2=counter2*0
counter3+=1
elif counter3>99:
counter3=counter3*0
counter4+=1
elif counter4>99:
counter4=counter4*0
counter5+=1
elif counter5>99:
counter5=counter5*0
counter6+=1
elif counter6>99:
counter6=counter6*0
counter7+=1
elif counter7>99:
counter7=counter7*0
counter8+=1
elif counter8>99:
counter8=counter8*0
counter9+=1
elif counter9>99:
print("DONE.")
# Printing the translation from counters to character - and deleting the former output so it stays in one line:
else:
print(lib[counter0]+lib[counter1]+lib[counter2]+lib[counter3]+lib[counter4]+lib[counter5]+lib[counter6]+lib[counter7]+lib[counter8]+lib[counter9], end="\r")
sys.stdout.write("\b"*10+" "*10+"\b"*10)
Réponses
Nous pouvons convertir une chaîne simple en une liste plutôt que de maintenir une liste pour les caractères.
Il est beaucoup plus facile de modifier et de lire ce qui suit puis une liste.
lib = [''] + list('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz°!"§$%&/()=ß´`+#-.,><@€|^~–{[]}ÄÖÜäöü')Quand nous avons
counter0,,counter1...,counternc'est un indice que nous devrions utiliser une liste.counters = [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0]Nous pouvons ensuite utiliser
counters[0]en remplacement decounter0.Maintenant que nous avons
countersune liste, nous pouvons simplifier votre impression à partir de ce qui suit.print(lib[counters[0]] + lib[counters[1]] + lib[counters[2]] + lib[counters[3]] + > lib[counters[4]] + lib[counters[5]] + lib[counters[6]] + lib[counters[7]] + lib[counters[8]] + lib[counters[9]], end="\r")Nous pouvons utiliser une boucle for pour parcourir les compteurs, indexer
libet imprimer le caractère. Nous utiliseronsend=""pour obtenir le même format que vous. Puisque nous sommes passés de"\r"à,""nous devrons l'imprimer par la suite.for counter in counters: print(lib[counter], end="") print(end="\r")Il serait préférable d'utiliser
len(lib)plutôt que de coder99en dur dans vos ifs. Si nous changeons le contenu de,libil est beaucoup plus facile d'éditer justelibplutôt quelib10 99.Au lieu de
counter0=counter0*0cela, il serait plus logique de supprimer la multiplication et de définir simplement la valeur sur 0.counter0 = 0C'est la convention en Python de mettre un espace de chaque côté des opérateurs. Cela signifie
a+bplutôt quea + b. C'est parce qu'il permet de voir plus facilement ce qui est et n'est pas un opérateur et de chaque côté d'un opérateur.C'est la convention en Python d'utiliser
_comme une variable «à jeter». Cela signifie qu'il est normal d'utiliser_plutôt queidans votre boucle for.
Ensemble, cela obtient:
import sys
lib = [''] + list('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz°!"§$%&/()=ß´`+#-.,><@€|^~–{[]}ÄÖÜäöü')
counters = [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
for _ in range(sys.maxsize**99999):
counters[0] += 1
if counters[0] >= len(lib):
counters[0] = 0
counters[1] += 1
elif counters[1] >= len(lib):
counters[1] = 0
counters[2] += 1
elif counters[2] >= len(lib):
counters[2] = 0
counters[3] += 1
elif counters[3] >= len(lib):
counters[3] = 0
counters[4] += 1
elif counters[4] >= len(lib):
counters[4] = 0
counters[5] += 1
elif counters[5] >= len(lib):
counters[5] = 0
counters[6] += 1
elif counters[6] >= len(lib):
counters[6] = 0
counters[7] += 1
elif counters[7] >= len(lib):
counters[7] = 0
counters[8] += 1
elif counters[8] >= len(lib):
counters[8] = 0
counters[9] += 1
elif counters[9] >= len(lib):
print("DONE.")
else:
for counter in counters:
print(lib[counter], end="")
print(end="\r")
sys.stdout.write("\b"*10 + " "*10 + "\b"*10)
Il y a encore quelques changements que nous pouvons apporter à votre code pour le rendre plus facile à utiliser. Ceux-ci sont un peu plus avancés alors ne vous inquiétez pas si vous ne les obtenez pas tout de suite.
Nous pouvons transformer votre gros
ifelifbloc en une seuleforboucle.
Voyons ce que nous avons jusqu'à présent:if counters[0] > len(lib): counters[0] = 0 counters[1] += 1On sait que cette section est répétée à chaque fois pour chaque index. Nous pouvons donc rendre cela générique en changeant
0enindexet1enindex + 1.if counters[index] >= len(lib): counters[index] = 0 counters[index + 1] += 1Il ne nous reste plus
range(len(counters) - 1)qu'à faire une boucle pour dupliquer le bloc 9 fois.for index in range(len(counters) - 1): if counters[index] >= len(lib): counters[index] = 0 counters[index + 1] += 1Nous pouvons utiliser du sucre Python pour rendre votre impression pour la boucle "plus propre". Premièrement, nous pouvons supprimer tous les
prints en construisant une liste.combination = [] for counter in counters: combination.append(lib[counter])De là, nous pouvons joindre toutes les chaînes
"".joinet les transmettreprintcomme vous l'avez fait auparavant. Cela rejoindra la liste par des chaînes vides, donc il convertit c'est comme faire manuellementcombination[0] + combination[1] + ....print("".join(combination), end="\r")Nous pouvons ensuite utiliser une compréhension de liste pour construire
combinationen une seule ligne. C'est juste du sucre syntaxique et c'est le même que la boucle for que nous avons utilisée auparavant. C'est juste une syntaxe différente et plus propre.combination = [lib[counter] for counter in counters]Nous pouvons utiliser soit une
while Trueboucle, soititertools.countplutôt qu'unerange(sys.maxsize**99999)boucle infinie.while True: counters[0] += 1import itertools for _ in range(itertools.count()): counters[0] += 1Nous pouvons probablement utiliser
printplutôt quesys.stdout.write.Pour faire en sorte qu'il n'y ait pas de nouvelle ligne que nous pouvons passer
end="". Cependant, cela ne vide pas le flux tout de suite, et nous devons donc passerflush=True.
lib = [''] + list('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz°!"§$%&/()=ß´`+#-.,><@€|^~–{[]}ÄÖÜäöü')
counters = [-1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
while True:
counters[0] += 1
for index in range(len(counters) - 1):
if counters[index] >= len(lib):
counters[index] = 0
counters[index + 1] += 1
if counters[9] >= len(lib):
print("DONE.")
else:
print("".join([lib[counter] for counter in counters]), end="\r")
print("\b"*10 + " "*10 + "\b"*10, end="", flush=True)
Il peut être utile de savoir que python a des options intégrées pour exécuter la combinatoire. En particulier, j'ai trouvé le module itertools très pratique pour ce genre d'opérations. Cela peut être un peu avancé lorsque vous commencez encore avec Python, mais avec le temps, vous découvrirez bon nombre de ces choses utiles.
Dans le cas du forçage brutal d'un mot de passe, la productméthode semble idéale.
Par exemple, si vous voulez toutes les combinaisons possibles avec 5 chiffres, vous pouvez exécuter:
from itertools import product
lib = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz°!"§$%&/()=ß´`+#-.,><@€|^~–{[]}ÄÖÜäöü'
for combination in product(lib, repeat=5):
attempt="".join(combination) # This turns combination (a list of characters) into a string.
# Use your password attempt here
Donc, si vous souhaitez étendre cela à tout le nombre de chiffres jusqu'à 10, vous pouvez utiliser:
for length in range(10):
for combination in product(lib, repeat=length):
attempt="".join(combination)
# Use your password attempt here
Une note sur l'utilisation des générateurs
Un avantage de cette méthode est que les méthodes de itertoolsne stockent pas toutes les combinaisons, mais les génèrent à la place. En conséquence, leur utilisation de la mémoire n'augmente pas avec le nombre de combinaisons.
C'est assez important dans un scénario comme le vôtre, où le nombre de combinaisons possibles a une croissance factorielle. Cet article donne une bonne introduction, avec quelques cas d'utilisation supplémentaires.
Une autre partie intéressante de ceci est qu'il est assez facile de laisser ce code continuer à essayer toutes les combinaisons de longueur croissante jusqu'à ce que quelque chose soit trouvé.
Cela utilise également la count()méthode d'itertools, qui est un générateur qui commence à partir d'un nombre et continue d'augmenter pour toujours.
from itertools import product, count
lib = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz°!"§$%&/()=ß´`+#-.,><@€|^~–{[]}ÄÖÜäöü'
for length in count(0):
for combination in product(lib, repeat=length):
attempt="".join(combination)
# Use your password attempt here
if password_found:
print(attempt)
break
Pour une meilleure convivialité (cela sera très pratique dans un instant), changeons votre code en générateur. C'est juste une fonction qui donne des valeurs une par une lorsqu'elle est demandée (ou plutôt, techniquement, l'objet générateur qu'elle renvoie le fait). Donc, le seul changement est qu'au lieu d'imprimer, vous cédez :
def combinations():
# List of 99 characters and a blank string:
...
else:
yield lib[counter0]+lib[counter1]+lib[counter2]+lib[counter3]+lib[counter4]+lib[counter5]+lib[counter6]+lib[counter7]+lib[counter8]+lib[counter9]
Maintenant, nous pouvons par exemple boucler sur ses résultats et les imprimer:
for comb in combinations():
print(comb)
Production:
0
1
2
3
4
5
6
7
8
9
A
B
...
Ou nous pouvons en prendre pour construire une liste:
from itertools import islice
print(list(islice(combinations(), 13)))
Production:
['', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B']
Regardons le passer d'un à deux personnages:
>>> list(islice(combinations(), 98, 102))
['ö', 'ü', '00', '10']
Ou de deux à trois:
>>> list(islice(combinations(), 99*100-1, 99*100+3))
['öü', 'üü', '10', '20']
Attends quoi? Pourquoi est 'üü'suivi de '10'? Cela n'aurait-il pas dû arriver beaucoup plus tôt ? Avons-nous produit cela deux fois?
>>> list(islice(combinations(), 99*100+3)).count('10')
2
Ouais nous l'avons fait. Oops. Il y a donc un bogue dans votre code. Beaucoup plus difficile à remarquer dans votre version, btw, avec toutes les combinaisons en cours d'impression et immédiatement écrasées :-)
Quoi qu'il en soit, je ne veux pas vraiment approfondir cela mais montrer une alternative simple. Commençons à partir de zéro. Pendant que nous y sommes, appelons-le wordset faisons de l'alphabet un paramètre. Commencez simplement, ne donnez que les mots de longueurs 0 et 1:
def words(alphabet):
yield ''
for letter in alphabet:
yield letter
Démo:
>>> list(words('abc'))
['', 'a', 'b', 'c']
Maintenant, comment produire les mots plus longs? Voyons ce que nous voulons:
'' '' + ''
'a' '' + 'a'
'b' '' + 'b'
'c' '' + 'c'
'aa' 'a' + 'a'
'ab' 'a' + 'b'
'ac' 'a' + 'c'
'ba' 'b' + 'a'
'bb' 'b' + 'b'
'bc' 'b' + 'c'
'ca' 'c' + 'a'
'cb' 'c' + 'b'
'cc' 'c' + 'c'
'aaa' 'aa' + 'a'
'aab' 'aa' + 'b'
'aac' 'aa' + 'c'
'aba' 'ab' + 'a'
'abb' 'ab' + 'b'
... ...
Sur la gauche se trouvent les mots comme nous les voulons, et sur la droite je les divise en préfixe et en dernière lettre (le cas échéant). Si nous regardons la dernière lettre, nous pouvons voir qu'elle continue de parcourir l'alphabet. Toutes les lettres pour chaque préfixe. Imaginons que nous ayons une prefixfonction qui nous a donné les préfixes. Ensuite, nous pourrions simplement écrire notre solution comme ceci:
def words(alphabet):
yield ''
for prefix in prefixes(alphabet):
for letter in alphabet:
yield prefix + letter
Mais attendez. Le premier préfixe est '', puis 'a', 'b', 'c', 'aa', 'ab', etc. Ainsi , le préfixe passe juste par la même séquence de mots que nous voulons ensemble. Donc ... notre wordsfonction peut s'utiliser pour produire les préfixes:
def words(alphabet):
yield ''
for prefix in words(alphabet):
for letter in alphabet:
yield prefix + letter
C'est tout. C'est toute la solution.
Démo:
>>> list(islice(words('abc'), 20))
['', 'a', 'b', 'c', 'aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca',
'cb', 'cc', 'aaa', 'aab', 'aac', 'aba', 'abb', 'abc', 'aca']
Enfin, essayons à nouveau avec votre alphabet et voyons que passer de deux à trois lettres:
>>> alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz°!"§$%&/()=ß´`+#-.,><@€|^~–{[]}ÄÖÜäöü'
>>> list(islice(words(alphabet), 99*100-1, 99*100+3))
['üö', 'üü', '000', '001']
Donc ... nous avons fini par implémenter le tout avec une fonction de générateur qui n'a que quatre lignes simples, et cela fonctionne avec un alphabet arbitraire, et en tant que générateur, il est facile à utiliser de plusieurs façons.
C'est probablement aussi beaucoup plus rapide que le vôtre, même si à cause de votre bogue, ce n'est pas facile à évaluer correctement. La version de Peilonrayz a également un bug pour le moment, mais nous pouvons comparer avec la solution d'Ivo_Merchiers et quelques variantes.
Dix millions de premiers mots en utilisant votre long alphabet de 99 lettres:
same first 9,999,999: True
same 10,000,000th: True {'9TT8'}
1.41 1.38 1.38 seconds Stefan_Pochmann
1.66 1.64 1.63 seconds Stefan_Pochmann_2
2.45 2.45 2.45 seconds Ivo_Merchiers
2.19 2.20 2.21 seconds Ivo_Merchiers_2
1.50 1.49 1.50 seconds Ivo_Merchiers_3
1.20 1.20 1.20 seconds Ivo_Merchiers_chain
Dix premiers millions de mots utilisant l'alphabet abc:
same first 9,999,999: True
same 10,000,000th: True {'abcaccbbcccacbc'}
2.49 2.43 2.48 seconds Stefan_Pochmann
4.16 4.17 4.19 seconds Stefan_Pochmann_2
3.91 3.91 3.93 seconds Ivo_Merchiers
3.64 3.66 3.64 seconds Ivo_Merchiers_2
2.74 2.74 2.75 seconds Ivo_Merchiers_3
2.45 2.46 2.45 seconds Ivo_Merchiers_chain
Code de référence complet:
from itertools import product, count, islice, chain
from timeit import repeat
from collections import deque
lib = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz°!"§$%&/()=ß´`+#-.,><@€|^~–{[]}ÄÖÜäöü'
def Stefan_Pochmann(alphabet):
yield ''
for prefix in Stefan_Pochmann(alphabet):
for letter in alphabet:
yield prefix + letter
def Stefan_Pochmann_2(alphabet):
yield ''
for prefix in Stefan_Pochmann_2(alphabet):
yield from map(prefix.__add__, alphabet)
def Ivo_Merchiers(lib):
for length in count(0):
for combination in product(lib, repeat=length):
yield ''.join(combination)
def Ivo_Merchiers_2(lib):
join = ''.join
for length in count(0):
for combination in product(lib, repeat=length):
yield join(combination)
def Ivo_Merchiers_3(lib):
for length in count(0):
yield from map(''.join, product(lib, repeat=length))
def Ivo_Merchiers_chain(lib): # from Peilonrayz
join = ''.join
return chain.from_iterable(
map(join, product(lib, repeat=length))
for length in count(0)
)
solutions = Stefan_Pochmann, Stefan_Pochmann_2, Ivo_Merchiers, Ivo_Merchiers_2, Ivo_Merchiers_3, Ivo_Merchiers_chain
for alphabet in lib, 'abc':
print(alphabet)
n = 10**7
# Correctness
sets = map(set, zip(*(words(alphabet) for words in solutions)))
print(f'same first {n-1:,}:', all(len(s) == 1 for s in islice(sets, n - 1)))
s = next(sets)
print(f'same {n:,}th:', len(s) == 1, s)
print()
# Speed
tss = [[] for _ in solutions]
for _ in range(3):
for words, ts in zip(solutions, tss):
t = min(repeat(lambda: deque(islice(words(alphabet), n), 0), number=1))
ts.append(t)
for words, ts in zip(solutions, tss):
print(*('%.2f' % t for t in ts), 'seconds ', words.__name__, sep=' ')
print()