Máquina de Babbage

Historia

A lo largo de los años se ha venido evidenciando la gran importancia de la criptografía para la evolución y el desarrollo del mundo desde sus inicios hasta la actualidad. Desde proteger nuestra información confidencial, hasta la protección y transmisión de mensajes de guerra son aplicaciones útiles que ha brindado la criptografía. Con la codificación de los cifrados ingenuos se comprenderá en el contexto histórico la relevancia que tuvo esta herramienta para dicha época. En contexto con el mundo moderno, se corroborará la importancia de utilizar un método de cifrado seguro con el fin de promover uno de los pilares de la seguridad de la información que es la confidencialidad a partir de la sencilla implemenetación de un cifrado antiguo muy reconocido denominado cifra de Vigenère.

Implementación

Se implementa el cifrado vigenere que fue roto por la máquina de babage en el lenguaje python. Se realiza el criptoanálisis obteniendo la llave para descifrar el mensaje automáticamente.
Se importa del paquete itertools el plugin cycle, con el fin de optimizar funciones que crean iteradores para recorrer de forma eficiente la lista donde se tiene almacenado el alfabeto y para otras utilidades tales como la función cycle que repite la llave automáticamente.
Zip es una función para reorganizar listas en python. Como parámetros admite un conjunto de listas. Lo que realmente hace es tomar el elemento iésimo de cada lista y unirlos en una tupla, después une todas las tuplas en una lista para manejar mas cómodamente la asignación de cada letra cifrada con su correspondiente letra en el alfabeto.
Se define el alfabético con que que va a trabajar el cifrado.
Hay una función para cifrar y otra para descifrar, iniciaremos explicando la función cifrar.
Esta recibe dos argumentos, la clave y el mensaje, una vez tenemos tanto el mensaje como la clave vinculadas en una lista en forma de tuplas, por medio de un ciclo se recorre la lista para calcular la letra correspondiente al alfabeto de babage, esto se logra realizar utilizando la siguiente función matemática en forma general:

Código


from functools import reduce
from itertools import cycle

string='''wubefiqlzurmvofehmymwtixcgtmpifkrzupmvoirqmmwozmpulmbnyvqqqmvmvjleimhfefnzpsdlppsdlpevqmw
cxymdavqeefiqcaytqowcxymwmsemefcfwyeyqetrliqycgmtwcwfbsmyfplrxtqyeexmruluksgwfptlrqaerlqvpmvyqycxtw
fqlmtelsfjpqehmozciwciwfpzslmaeziqvlqmzvppxawcsmzmorvgwwqszetrlqzpbjazvqiyxewwoiccgdwhqmmvowsgntjpf
ppaibiybjutwelqklllmdpyvacdcfqnzpifppksdvptidgxmqqvebmqalkezmgcvkuzkizbzliuammvz'''
gramLength=4 # Pattern Length

locations = []; #Lista para las posiciones de los repetidos
distances = []; # Espaciamientos de los repetidos
def longitudLlave(string,gramLength):	
    #Asiganacion del subpatron
    for i in range(len(string)):
        gram = string[i:i+gramLength];	
        # make sure we don't get stuck with shorties
        if len(gram) < gramLength: break #Validacion final del subpatron
        locations.append([]); #Inicializa una lista dentro de una lista vacia.
    
        find = string.find(gram,0); # PENDIENTE
        while find != -1: 
            locations[i].append(find); #Cuando encuentre un patron repetitivo lo añade a lista
            find = string.find(gram,find+1); #Desplazamiento del subpatron
        	
        	
        if len(locations[i]) > 1:  #SI hay dos o mas repeticiones, se calcula la distancia
             for j in range(1,len(locations[i])):
                 distances.append(locations[i][j] - locations[i][0]) #Hallar la distancia Yf - Yi    
    miMcd = McdArray(distances); # MCD de las distancias
    return miMcd  # Tamaño de la llave
print(distances) #


def mcd(a,b):# calcular MCD
	while b != 0:
		t = b;
		b = a % b;
		a = t;
	return a


def McdArray(mcdz):# Si recibe un array y lo calcula

	""" Takes the greatest common divisor of an entire array """

	if len(mcdz) < 2: return mcdz[0]; #El MCD es el mismo numero (Cuando el numero es indivisible)
	
	miMcd = mcd(mcdz[0],mcdz[1]);	#Se empieza a hallar el MCD con los dos primeros numeros, con el mod

	for i in range(2,len(mcdz)):  # Es el mismo de arriba, pero con las posiciones siguientes hasta el final	
		miMcd = mcd(mcdz[i],miMcd);

	return miMcd;
 
def separarString(cipherText,longitudLlave): #Se debe separar el texto cifrado en subtextos del tamaño de la llave.
        freqList=[] #Lista donde se guardan las divisiones del texo cifrado
        
        for i in range(longitudLlave):freqList.append(''); # se inicializa la llave con strings vacios, con el fin de que la reconozca como string y no como int

        for i in range(len(cipherText)):  
            index=i%longitudLlave  #Correspondencia del texto cifrado con su letra del alfabeto que se cifro.(6%5= ) Toma la letra del texto cifrado y la pone en las posiciones donde deben estar para poderlas separar como las pide babage, se calcula el indice para saber donde se acomoda"""
            freqList[index]+=cipherText[i] #Aca se acomodan letras de acuerdo al indice calculado anteriormente
            
        return freqList # parte dividida del texto


def calcProb(freqList):
     cNums = map(chr, range(97, 123))   #Se crea un array con ciertos tipos de datos, array de caracteres del rango 97 al 123 que son las letras minusculas en ASCII
     prob = [] #Vector de vectores vacio
     f = range(len(cNums))  #Lista que contiene las letras en ascii
     p = [.082,.015,.028,.043,.127,.022,.020,.061,.070,.002,.008,.040,.024,.067,.075,.019,.001,.060,.063,.091,.028,.010,.023,.001,.020,.001];  #Probabilidad de ocurrencia del letras en el idioma ingles

     for i in range(len(cNums)): 
         f[i] = freqList.count(cNums[i]) # la cantidad de letras que hay en cada posicion de freq list, parte divididda del texto count( letra que se repite) Para cada subcadena del ciphertext
         prob.append(0)  #

     print (f)
	#parte clave
     for g in range(len(cNums)): #Para recorrer el vector de probabilidad (prob)
         #print g
         for i in range(len(cNums)): #Para moverse en el vector de probabilidades del idioma ingles (p)
             #print i
             fig = f[(i+g)% len(f)]  #COmo una sumatoria de la prob del acento ingles por  (OPERACION CLAVE)
             prob[g] += ((p[i]*fig) / float(len(freqList)))   #(OPERACION CLAVE)
             #print "pos",(i+g)% len(f) 
             #print "prob",prob  
             
             #print(p[i])
     return prob;  #retorna el vector de probabilidad


def sugKey(prob): # EL metodo que encuentra la llave, da una llave dependiendo de las probabilidades mas altas que hayan en cada vector de vectores
	
	""" Suggestes a key, given the maximum likelihood """
	cNums = map(chr, range(97, 123))

	i = max(prob) # encuentra el numero mas grande en un array
      
	return cNums[prob.index(i)]  #devuelve la letra en la posicion i 


def criptoAnalisis(string,n):
	
	y = separarString(string,n) # se separa en partes de acuerdo al tamaño de la llave
	
	probTotal = [] #Probabilidades finales

	for i in range(n): 
         m = calcProb(y[i])  #Llama al metodo para calcular las probabilidades
         probTotal.append(m)
         print (probTotal)		

	# suggest the key
	key = "";
	for i in range(n):
		key += sugKey(probTotal[i]) #COrrespondencia de la letra que tiene mayor probabilidad en el texto cifrado con la de mayor probabilidad del ifioma ingles

	return key;  #Retorna la anhelada llave

def decrypt(ciphermessage, key):
    pairs = zip(ciphermessage, cycle(key))
    cNums = map(chr, range(97, 123)) # Crea una lista con los elementos que uno le mande en los argumentos
    result = ''

    for i in pairs:
        total = reduce(lambda x, y: cNums.index(x) - cNums.index(y), i) # DOnde se evalua la operacion rara con el mod
        result += cNums[total % 26]  # operacion de desencriptado de vigenere

    return result
        
        
    
v=longitudLlave(string,gramLength)
print (v)
x=separarString(string,v)
a=criptoAnalisis(string,v)
print ("llave:",a)
dec=decrypt(string,a)
print (dec)

Nota* (Este script fue realizado con python 2)