En este post incluyo y comento un script en python para el cifrado y descifrado utilizando el cifrado de Playfair.
Antes de poner el script, comentar cómo se cifraba y descifraba manualmente.
El cifrado de Playfair consistía en utilizar una matriz de 5x5 caracteres que se iba rellenando, de izquierda a derecha y de arriba a abajo, con las letras de la clave (eliminando las posibles letras repetidas de la misma) y se completaba con el resto de las letras del alfabeto (las que no figurasen en la clave), excluidas la "J" y la "Ñ".
Por ejemplo, utilizando la palabra "PLAYFAIR" como clave, obtendríamos la matriz que se muestra a continuación:
- Cifrar:
Para cifrar, para cada par de caracteres, ya que en este criptosistema se realiza una sustitución digrámica, se actuaba de la siguiente forma:
- Si ambos caracteres estaban situados en la misma fila cada uno de ellos se sustituía por el carácter situado inmediatamente a su derecha (si el carácter a sustituir era el último de la fila se sustituía por el primero de la fila).
- Si ambos caracteres estaban situados en la misma columna cada uno de ellos se sustituía por el carácter situado inmediatamente debajo de él (si el carácter a sustituir era el último de la columna se sustituía por el primero de la columna).
- Si ambos caracteres estaban situados en diferente fila y diferente columna, la primera letra se sustituía por aquella situada en su misma fila y la misma columna que la segunda, y la segunda por aquella situada en su misma fila y la misma columna que la primera.
En el script el cifrado se implementa de la siguiente manera:
- Se simula la matriz de 5x5 mediante una cadena de caracteres a la que se van agregando, en primer lugar, los caracteres de la clave, sin incluir los posibles caracteres repetidos en la misma, y, posteriormente, el resto de caracteres del alfabeto. Todo ello, mediante los dos siguientes bucles:
for caracter in clave:
if caracter not in matriz:
matriz=matriz+caracter
# Agregar a la cadena el resto de caracteres del alfabeto.
for caracter in alfabeto:
if caracter not in matriz:
matriz=matriz+caracter
El alfabeto empleado es: "ABCDEFGHIKLMNOPQRSTUVWXYZ" (nótese que no figuran la letra "J", que compartiría posición con la "I", ni la "Ñ"), por lo que se obtendrá una cadena de 25 caracteres de longitud que simula la matriz de 5 filas x 5 columnas.
En el ejemplo puesto anteriormente:
"PLAYFIRBCDEGHKMNOQSTUVWXZ"
- Las parejas de caracteres que se cifrarán serán los pares de caracteres consecutivos del texto plano, pero si algún par está formado por letras iguales hay que incluir un carácter de relleno entre ambas (por ejemplo: "X", que es una letra de baja frecuencia). Al finalizar lo anterior, si la cadena resultado tiene una longitud impar hay que añadirle el carácter de relleno (en nuestro caso "X") al final, ya que como vamos a realizar una sustitución digrámica (de grupos de dos caracteres), lógicamente, la longitud de la cadena debe ser par:
Esto se implementa en el script de la siguiente manera:
len(): la función len() devuelve el número de items de un objeto. Cuando el objeto es una cadena devuelve el número de caracteres de la misma.
En el script devuelve el número de caracteres de la cadena con el texto plano y, cuando finaliza de incluir caracteres de relleno, la de la cadena resultado de añadir éstos. En el ejemplo que veremos a continuación 23 y 31, respectivamente. Nótese que la última longitud indica será justo después de incluir los caracteres de relleno, y como es impar se añadirá una última "X" a la cadena resultado.
%: el operador % realiza la división modular entre dos operandos, es decir, obtiene el resto de dividir el operando 1 entre el operando 2.
i=0
while i < len(texto):
if i != len(texto)-1:
if texto[i]==texto[i+1]:
parejas+=texto[i]+'X'
i-=1
else:
parejas+=texto[i]+texto[i+1]
else:
parejas+=texto[i]
i+=2
if len(parejas)%2!=0:
parejas+='X'
Para ver cómo se van incluyendo los caracteres de relleno, supongamos que la cadena con el texto en claro cifrar es:
"AABBCBCCCDDEFFFFEEGGGHH",
por lo que se obtendría la siguiente cadena:
"AXABBXBCCXCXCDDEFXFXFXFEEGGXGHHX"
- Y, finalmente y conforme a la matriz con la clave, se cifran los pares de letras consecutivos de la cadena obtenida en el paso anterior, en función de que los caracteres que conforman cada par estén en la misma fila de la matriz, estén en la misma columna o se sitúen en distinta fila y distinta columna.
En el script:
//: el operador // obtiene el cociente entero de dividir el operando 1 entre el operando 2.
matriz.find(parejas[i])//5: el método find() encuentra la primera ocurrencia del valor especificado en una cadena de caracteres y devuelve la posición que ésta ocupa. La primera posición es 0 y si no se encuentra el valor devuelve -1.
Para cada par de caracteres a cifrar:
Si las letras están en la misma fila en la matriz con la clave, es decir, si el cociente entero de dividir las sendas posiciones que ocupan las letras de la pareja entre 5 son iguales (if matriz.find(parejas[i])//5==matriz.find(parejas[i+1])//5:) bastará con sumar 1 a sus respectivas posiciones en la matriz de la clave para obtener las correspondientes letras por la que será sustituida cada una de ellas, considerando que si alguna de ellas se encuentra en la última columna debe ser sustituida por la primera letra de la fila en la que se encuentre:
criptograma=criptograma+matriz[matriz.find(parejas[i])+1]
else:
criptograma=criptograma+matriz[matriz.find(parejas[i])-4]
if matriz.find(parejas[i+1])%5!=4:
criptograma=criptograma+matriz[matriz.find(parejas[i+1])+1]
else:
criptograma=criptograma+matriz[matriz.find(parejas[i+1])-4]
En el ejemplo que hemos puesto anteriormente, para la cuarta pareja "BC": 7//5 = 1 es igual que 8//5 = 1, por lo que ambas letras están en la misma fila; la "B" sería sustituida por "C" y "C" por "D".
Si las letras están en la misma columna en la matriz con la clave, es decir: si el resto de dividir las sendas posiciones que ocupan las letras de la pareja entre 5 son iguales (if matriz.find(parejas[i])%5==matriz.find(parejas[i+1])%5:) bastará con sumar 5 a sus respectivas posiciones en la matriz de la clave para obtener las correspondientes letras por la que será sustituida cada una de ellas, considerando que si alguna de ellas se encuentra en la última fila debe ser sustituida por la primera letra de la columna en la que se encuentre:
criptograma=criptograma+matriz[(matriz.find(parejas[i+1])+5)%25]
En el ejemplo que hemos puesto anteriormente, para la segunda pareja "AB": 2%5 = 2 es igual que 7%5 = 2, por lo que ambas letras están en la misma columna; la "A" sería sustituida por "B" y "B" por "H".
Por el contrario, si las letras están en distinta fila y distinta columna en la matriz con la clave, es decir, si no se ha satisfecho ninguna de las condiciones anteriores, bastará con obtener la distancia entre las columnas en las que están situadas ambas letras (columna segunda letra de la pareja - columna primera letra de la pareja) y sumar dicha distancia a la posición en la que se encuentra la primera de ellas para obtener la letra que la sustituirá, y restar dicha distancia a la posición en la que se encuentra la segunda de ellas para obtener la letra que la sustituirá.
En el ejemplo que hemos puesto anteriormente, las letras de la primera pareja, "AX", están en diferente fila y diferente columna, y la distancia entre ambas columnas es: 3 - 2 = 1, por lo que la letra "A" se sustituirá por "Y" (2 + 1 = 3) y la "X" por "W" (23 - 1 = 22).
- Descifrar:
Para descifrar, partiendo del criptograma, se aplican las mismas reglas del cifrado teniendo en cuenta que si una letra del criptograma se obtuvo por ser la siguiente a la del texto plano en la tabla de la matriz, ahora la letra del texto plano será la anterior a la del criptograma, etc.
- Script python del cifrado de Playfair:
El script es el siguiente:
#!/usr/bin/env python # -*- coding: utf-8 -*- # CIFRADO DE PLAYFAIR # # Cifra y descifra textos en claro y criptogramas, respectivamente, # utilizando el cifrado de Playfair. # # http://mikelgarcialarragan.blogspot.com/ import re from unicodedata import normalize # ALFABETO: alfabeto = "ABCDEFGHIKLMNOPQRSTUVWXYZ" # FUNCIÓN DE CIFRADO: def cifrar(texto_claro, clave): criptograma = '' matriz=crear_matriz(clave) parejas=establecer_parejas(texto_claro) for i in range(0,len(parejas)-1,2): if matriz.find(parejas[i])//5==matriz.find(parejas[i+1])//5: # Los caracteres de la pareja están en la misma fila. if matriz.find(parejas[i])%5!=4: criptograma=criptograma+matriz[matriz.find(parejas[i])+1] else: criptograma=criptograma+matriz[matriz.find(parejas[i])-4] if matriz.find(parejas[i+1])%5!=4: criptograma=criptograma+matriz[matriz.find(parejas[i+1])+1] else: criptograma=criptograma+matriz[matriz.find(parejas[i+1])-4] elif matriz.find(parejas[i])%5==matriz.find(parejas[i+1])%5: # Los caracteres de la pareja están en la misma columna. criptograma=criptograma+matriz[(matriz.find(parejas[i])+5)%25] criptograma=criptograma+matriz[(matriz.find(parejas[i+1])+5)%25] else: # Los caracteres de la pareja están en distinta fila y distinta columna. distancia_columnas=matriz.find(parejas[i+1])%5-matriz.find(parejas[i])%5 criptograma=criptograma+matriz[matriz.find(parejas[i])+distancia_columnas]+matriz[matriz.find(parejas[i+1])-distancia_columnas] return criptograma # FUNCIÓN DE DESCIFRADO: def descifrar(criptograma, clave): texto_claro = '' matriz=crear_matriz(clave) parejas=establecer_parejas(criptograma) for i in range(0,len(parejas)-1,2): if matriz.find(parejas[i])//5==matriz.find(parejas[i+1])//5: # Los caracteres de la pareja están en la misma fila. if matriz.find(parejas[i])%5!=0: texto_claro=texto_claro+matriz[matriz.find(parejas[i])-1] else: texto_claro=texto_claro+matriz[matriz.find(parejas[i])+4] if matriz.find(parejas[i+1])%5!=0: texto_claro=texto_claro+matriz[matriz.find(parejas[i+1])-1] else: texto_claro=texto_claro+matriz[matriz.find(parejas[i+1])+4] elif matriz.find(parejas[i])%5==matriz.find(parejas[i+1])%5: # Los caracteres de la pareja están en la misma columna. if matriz.find(parejas[i])//5!=0: texto_claro=texto_claro+matriz[matriz.find(parejas[i])-5] else: texto_claro=texto_claro+matriz[matriz.find(parejas[i])+20] if matriz.find(parejas[i+1])//5!=0: texto_claro=texto_claro+matriz[matriz.find(parejas[i+1])-5] else: texto_claro=texto_claro+matriz[matriz.find(parejas[i+1])+20] else: # Los caracteres de la pareja están en distinta fila y distinta columna. distancia_columnas=matriz.find(parejas[i+1])%5-matriz.find(parejas[i])%5 texto_claro=texto_claro+matriz[matriz.find(parejas[i])+distancia_columnas]+matriz[matriz.find(parejas[i+1])-distancia_columnas] return texto_claro # CREAR UNA CADENA CON LOS CARACTERES DE LA MATRIZ DE 5x5 UTILIZANDO LA CLAVE: def crear_matriz(clave): # Agregar a una cadena los caracteres de la clave no repetidos. matriz='' for caracter in clave: if caracter not in matriz: matriz=matriz+caracter # Agregar a la cadena el resto de caracteres del alfabeto. for caracter in alfabeto: if caracter not in matriz: matriz=matriz+caracter return matriz # ESTABLECER PAREJAS DE CARACTERES: def establecer_parejas(texto): parejas='' i=0 while i < len(texto): if i != len(texto)-1: if texto[i]==texto[i+1]: parejas+=texto[i]+'X' i-=1 else: parejas+=texto[i]+texto[i+1] else: parejas+=texto[i] i+=2 if len(parejas)%2!=0: parejas+='X' return parejas # MENÚ: # Se presenta el menú para que se seleccione una opción. def main(): salir = False while not salir: print ("") print ("*** MENÚ *****************************************") print ("1. Cifrar.") print ("2. Descifrar.") print ("3. Salir.") print ("") opcion = input("Por favor, seleccione una opción: ") if opcion == "1": print ("") print ("--- CIFRAR:") # Cifrar: Se introducen el texto en claro y la clave. Se convierten los caracteres a mayúsculas, # el carácter "J" se sustituye por "I" y la letra "Ñ" por "N", y se eliminan de ambos los espacios, # las tildes, diéresis, etc. texto_claro = clave = "*" while not texto_claro.isalpha(): texto_claro = input('Texto en claro a cifrar: ').upper() texto_claro = texto_claro.replace('J','I') texto_claro = texto_claro.replace('Ñ','N') texto_claro = texto_claro.replace(' ','') texto_claro = re.sub(r"([^n\u0300-\u036f]|n(?!\u0303(?![\u0300-\u036f])))[\u0300-\u036f]+", r"\1", normalize("NFD", texto_claro), 0, re.I) texto_claro = normalize("NFC", texto_claro) if texto_claro.isalpha(): print ("[+] Texto en claro a cifrar:", texto_claro) while not clave.isalpha(): clave = input('Clave: ').upper() clave = clave.replace('J','I') clave = clave.replace('Ñ','N') clave = clave.replace(' ','') clave = re.sub(r"([^n\u0300-\u036f]|n(?!\u0303(?![\u0300-\u036f])))[\u0300-\u036f]+", r"\1", normalize("NFD", clave), 0, re.I) clave = normalize("NFC", clave) if clave.isalpha(): print ("[+] Clave:", clave) criptograma = cifrar(texto_claro, clave) print ("[+] Criptograma:", criptograma) else: print ("*** ERROR: La clave sólo debe contener caracteres alfabéticos.") else: print ("*** ERROR: El texto en claro a cifrar sólo debe contener caracteres alfabéticos.") elif opcion == "2": print ("") print ("--- DESCIFRAR:") # Descifrar: Se introducen el criptograma y la clave. Se convierten los caracteres a mayúsculas, # el carácter "J" se sustituye por "I" y la letra "Ñ" por "N", y se eliminan de ambos los espacios, # las tildes, diéresis, etc. criptograma = clave = "*" while not criptograma.isalpha(): criptograma = input('Criptograma a descifrar: ').upper() criptograma = criptograma.replace('J','I') criptograma = criptograma.replace('Ñ','N') criptograma = criptograma.replace(' ','') criptograma = re.sub(r"([^n\u0300-\u036f]|n(?!\u0303(?![\u0300-\u036f])))[\u0300-\u036f]+", r"\1", normalize("NFD", criptograma), 0, re.I) criptograma = normalize("NFC", criptograma) if criptograma.isalpha(): print ("[+] Criptograma a descifrar:", criptograma) while not clave.isalpha(): clave = input('Clave: ').upper() clave = clave.replace('J','I') clave = clave.replace('Ñ','N') clave = clave.replace(' ','') clave = re.sub(r"([^n\u0300-\u036f]|n(?!\u0303(?![\u0300-\u036f])))[\u0300-\u036f]+", r"\1", normalize("NFD", clave), 0, re.I) clave = normalize("NFC", clave) if clave.isalpha(): print ("[+] Clave:", clave) texto_claro = descifrar(criptograma, clave) print ("[+] Texto en claro:", texto_claro) else: print ("*** ERROR: La clave sólo debe contener caracteres alfabéticos.") else: print ("*** ERROR: El criptograma a descifrar sólo debe contener caracteres alfabéticos.") elif opcion == "3": print ("*** FIN ******************************************") salir = True else: print ("*** ERROR: Opción no válida.") if __name__ == '__main__': main()
Lo ejecuto:
- Cifrar:
Nótese que el script ha sustituido el carácter "j" que se ha introducido en el texto en claro a cifrar por "I", ya que "I" y "J" comparten la misma posición en la matriz con la clave. Además, si se hubiera introducido la letra "Ñ" el script la habría sustituido por "N".
- Descifrar:
Comentarios
Publicar un comentario