Брутфорс хэша крекер


Я сделал хэш шутиха в Python (в чисто воспитательных целях), но это очень долго (~120 секунд для 4-буквенная). Как я могу ускорить его?

Текущая оптимизация и пояснения:

  • Закрытие в CharSet.get_advance: это быстрее, чем поиск атрибутов.
  • iter в PasswordCracker.crack: это перемещает петлю на С.
  • CharSet.next как array.array: быстрее, чем dict.

Возможная будущая оптимизация:

  • advance это медленно, но я не уверен, как его ускорить.

Код:

import hashlib
from string import printable
from time import time
import itertools
from array import array

ENCODING = "ascii" # utf-8 for unicode support

class CharSet():
  def __init__(self, chars):
    chars = to_bytes(chars)
    self.chars = set(chars)
    self.first = chars[0]
    self.last = chars[-1]
    self.next = array("B", [0] * 256)
    for char, next_char in zip(chars, chars[1:]):
      self.next[char] = next_char

  def update_chars(self, new_chars):
    new_chars = to_bytes(new_chars)
    new_chars = set(new_chars) - self.chars
    if new_chars: # if theres anything new
      self.chars |= new_chars
      new_chars = list(new_chars)
      self.next[self.last] = new_chars[0]
      self.last = new_chars[-1]
      for char, next_char in zip(new_chars, new_chars[1:]):
        self.next[char] = next_char

  def get_advance(self, arr, hash_):
    first = self.first
    last = self.last
    next_ = self.next
    def advance():
      for ind, byte in enumerate(arr):
        if byte == last:
          arr[ind] = first
        else:
          arr[ind] = next_[byte]
          return hash_(arr)

      arr.append(first)
      return hash_(arr)

    return advance

class PasswordCracker():
  def __init__(self, hash_, chars=None):
    self.hash = hash_
    if chars is None:
      chars = printable
    self.char_set = CharSet(chars)

  def update_chars(self, string):
    self.char_set.update_chars(string)

  def crack(self, hashed):
    arr = bytearray()
    advance = self.char_set.get_advance(arr, self.hash)
    for _ in iter(advance, hashed):
      pass
    return arr

def to_bytes(string):
  if isinstance(string, str):
    return bytearray(string, ENCODING)
  elif isinstance(string, (bytes, bytearray)):
    return string
  else:
    raise TypeError(f"Cannot convert {string} to bytes")

def get_hasher(hash_):
  def hasher(bytes):
    return hash_(bytes).digest()

  return hasher

md5 = get_hasher(hashlib.md5)

cracker = PasswordCracker(md5)

password = input("Enter password: ")

cracker.update_chars(password)
password = md5(to_bytes(password))

start = time()
cracked = cracker.crack(password)
end = time()
print(f"Password cracked: {cracked.decode(ENCODING)}")
print(f"Time: {end - start} seconds.")

Результаты профилирования (с паролем "pww"):

      1333313 function calls in 1.500 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.500    1.500 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 main.py:31(get_advance)
   333326    0.394    0.000    1.376    0.000 main.py:35(advance)
        1    0.124    0.124    1.500    1.500 main.py:58(crack)
   333326    0.311    0.000    0.982    0.000 main.py:74(hasher)
   333326    0.265    0.000    0.265    0.000 {built-in method _hashlib.openssl_md5}
        1    0.000    0.000    1.500    1.500 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.iter}
        3    0.000    0.000    0.000    0.000 {method 'append' of 'bytearray' objects}
   333326    0.405    0.000    0.405    0.000 {method 'digest' of '_hashlib.HASH' objects}

Результаты профилирования (с паролем "pwww"дополнительные "w"):

         133333314 function calls in 190.800 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000  190.799  190.799 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 main.py:31(get_advance)
 33333326   65.652    0.000  169.782    0.000 main.py:35(advance)
        1   21.017   21.017  190.799  190.799 main.py:58(crack)
 33333326   40.640    0.000  104.130    0.000 main.py:74(hasher)
 33333326   27.957    0.000   27.957    0.000 {built-in method _hashlib.openssl_md5}
        1    0.000    0.000  190.800  190.800 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.iter}
        4    0.000    0.000    0.000    0.000 {method 'append' of 'bytearray' objects}
 33333326   35.533    0.000   35.533    0.000 {method 'digest' of '_hashlib.HASH' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


1725
8
задан 18 февраля 2018 в 11:02 Источник Поделиться
Комментарии
2 ответа

Используя правильный инструмент для работы

Это легко Проблема в коде, но трудно решить с помощью компьютера. Лучше использовать низкий уровень-язык, как C.

Генерация возможных паролей

Вам не нужно создавать пароли вручную, лучше использовать модуле itertools библиотека.

from hashlib import md5
from time import time
from string import printable
from itertools import product, count

def passwords(encoding):
chars = [c.encode(encoding) for c in printable]
for length in count(start=1):
for pwd in product(chars, repeat=length):
yield b''.join(pwd)

def crack(search_hash, encoding):
for pwd in passwords(encoding):
if md5(pwd).digest() == search_hash:
return pwd.decode(encoding)

if __name__ == "__main__":
encoding = 'ascii' # utf-8 for unicode support
password = 'pwww'
password_hash = md5(password.encode(encoding)).digest()

start = time()
cracked = crack(password_hash, encoding)
end = time()
print(f"Password cracked: {cracked}")
print(f"Time: {end - start} seconds.")

Импорт

Как правило, лучшим вариантом является from x import yно здесь можно уменьшить кэш

# import hashlib # usually bad one
# from hashlib import md5 # usually best one
from _md5 import md5 # ugly hack but speed up

2
ответ дан 4 марта 2018 в 03:03 Источник Поделиться

Я понимаю, что это в учебных целях, и вы заинтересованы в выполнении этой конкретной реализации. В противном случае я хотел сказать вам, что вычисления хэшей каждый раз может быть немного медленнее, чем хранить их.

Не будет ли быстрее, чтобы создать список возможных паролей в первую очередь? Параллельность и чрезмерное усложнение может сделать эту часть медленнее, я на 99,9% уверен, но это будет установить Вас для некоторых хороших параллелизма за все остальное.

from itertools import product
passwords = product(printable, repeat = 4)

Для меня. с диапазоном(0,255) вместо печати занимает 1,5 секунды.

Затем вы могли бы использовать бассейн.карту в мультипроцессорной обработки.манекен, чтобы взять вас остаток пути -> создание + проверьте контрольные суммы. (см. https://stackoverflow.com/a/28463266/8695782| для справки, я думаю, что для создания хэша и проверка параллельности может помочь). Я, например, предпочел бы идти в направлении поиска-тип структуры, я хочу, чтобы за O(1) на поиск, после генерации/хранения+перегруз.

Я могу понять, почему целей обучения может сделать вы не хотите сохранить "радужных таблиц" и ограничить использование памяти, но помню, что, когда дело доходит до производительности, всегда будет скорость против пространство компромиссов. И говоря о пространстве, почему все 255 символов, по крайней мере, исключить некоторые управляющие символы. Вы можете протестировать ваш код против https://gizmodo.com/over-560-million-passwords-discovered-by-security-resea-1795254560

1
ответ дан 3 марта 2018 в 05:03 Источник Поделиться