Criptografia: Cifra Playfair em Python

A cifra Playfair, também conhecida como quadrado de Playfair, foi uma cifra inventada por Charles Wheastone em 1854, mas que recebeu seu nome por causa de Lyon Playfair ou Lorde Playfair, que conseguiu que fosse utilizada pelo governo britânico. É uma cifra de substituição e cuja técnica principal consiste no uso de digramas ou bigramas (um par de letras) no processo de cifragem/decifragem.

Seu princípio de funcionamento usa uma tabela 5×5 preenchida com as 26 letras do alfabeto latino (como a tabela é um quadrado de 5×5, resultando em 25 células, optamos por unir as letras i e j numa mesma célula, onde todo j na mensagem a ser cifrada será substituído por i) e uma palavra-chave ou senha.

Usando como senha a palavra ESPIAO, preenchemos a tabela como mostrado abaixo:

   1 2 3 4 5
1 |E|S|P|I|A|
2 |O|B|C|D|F|
3 |G|H|K|L|M|
4 |N|Q|R|T|U|
5 |V|W|X|Y|Z|

A primeira etapa da cifragem consiste na divisão da mensagem em digramas (pares de letras). Assim, a frase temos um agente infiltrado, ficará assim:

te mo su ma ge nt ei nf il tr ad ow

Quando dividimos a frase em digramas, vemos que a última letra da frase ficou sozinha. Assim, adicionamos uma letra (adotarei o w) a ela para formar o par.

A segunda etapa é pegarmos cada par de letras e as localizarmos na tabela.

Há três regras para cifrar um par de letras.

1) se as letras estão na mesma linha, cada letra é trocada pela letra da coluna da direita; caso a letra esteja no final da linha troca-se pela primeira letra da mesma linha. Exemplo, o par nt (n linha 4 e coluna 1, t linha 4 e coluna 4 – em vermelho) é trocado por QU (Q linha 4 e coluna 2, U linha 4 e coluna 5 – em verde);

   1 2 3 4 5
1 |E|S|P|I|A|
2 |O|B|C|D|F|
3 |G|H|K|L|M|
4 |N|Q|R|T|U|
5 |V|W|X|Y|Z|

2) se as letras estão na mesma coluna, cada letra é trocada pela letra da linha logo abaixo; caso a letra esteja no final da coluna, troca-se a letra pela letra do início da mesma coluna. Exemplo, o par il (i L1 C4, l L3 C4) é trocado por DT (D L2 C4, T L4 C4);

   1 2 3 4 5
1 |E|S|P|I|A|
2 |O|B|C|D|F|
3 |G|H|K|L|M|
4 |N|Q|R|T|U|
5 |V|W|X|Y|Z|

3) já, se as letras estiverem em linhas e colunas diferentes, a troca se dará assim: a primeira letra será trocada pela letra que estiver na mesma linha da primeira letra e na coluna da segunda letra; a segunda letra será trocada pela letra que estiver na mesma linha da segunda letra e na coluna da primeira letra. Exemplo, o par ow (o L2 C1, w L5 C2) é trocado por BV (B L2 C2, V L5 C1).

   1 2 3 4 5
1 |E|S|P|I|A|
2 |O|B|C|D|F|
3 |G|H|K|L|M|
4 |N|Q|R|T|U|
5 |V|W|X|Y|Z|

Assim, nossa mensagem cifrada ficará assim:

NIGFAQUFNOQUSAUODTUTIFBV

Vale uma observação. Pode ocorrer o caso quando dividimos a frase em digramas, de um ou mais digramas ficarem com letras iguais. Neste caso, o digrama será separado e acrescentada uma letra entre eles (no meu caso, resolvi adicionar a letra w).

Por exemplo, a frase nossa esperança é a sua vinda, os digramas ficariam assim:

no ss ae sp er an ca ea su av in da

Como há o digrama ss, acrescentamos o w entre eles e nossa frase ficará assim:

no sw sa es pe ra nc ae as ua vi nd aw

E o texto cifrado:

NIGFAQUFNOQUSAUODTUTIFBV

CÓDIGO

O código foi salvo no arquivo playfair.py e precisa do arquivo cipher.py (que está abaixo e é utilizada em alguns outras cifras publicadas no blog).

# -*- coding: utf-8 -*-
from cipher import Cipher

class Playfair(Cipher):
    def encrypt(self, text, key = '', decrypt = False):
        """Retorna text cifrado com a cifra de Playfair.
        key - chave a ser usada; havendo chave, o alfabeto sera iniciado com ela
        decrypt - se True, decifra; se False, cifra
        """
        ciphertext = ''
        text = text.upper()
        text = text.replace('J', 'I').replace(' ', '')
        # checa se o texto contem par de letras iguais
        text = self.check_double(text)
        # se text nao tiver numero par de letras
        if len(text) % 2:
            text += 'W'
        # cria a tabela
        self.square = self.create_square(key)
        for i in range(0, len(text), 2):
             ciphertext += self.change_pair(text[i:i + 2], decrypt)
        return ciphertext.upper()

    def decrypt(self, ciphertext, key = ''):
        """Retorna o ciphertext decifrado com a cifra de Playfair"""
        return self.encrypt(ciphertext.upper(), key, True).lower()

    def check_double(self, text):
        out = ''
        for idx in range(0, len(text), 2):
            pair = text[idx:idx+2]
            if len(pair) > 1 and pair[0] == pair[1]:
                out += pair[0] + 'W' + pair[1]
            else:
                out += pair
        return out

    def change_pair(self, pair, decrypt = False):
        """Retorna as posicoes de um par de letras de uma matriz 5x5."""
        # retorno - valor a ser retornado quando se atinge o limite da tabela
        # limite - valor de limite das celulas da tabela
        # passo - valor de incremento/decremento
        if decrypt:
            retorno = 4
            limite = -1
            passo = -1
        else:
            retorno = 0
            limite = 5
            passo = 1
        coord1, coord2 = self.coords(pair)
        if coord1[0] == coord2[0]:
            # caso em que as duas letras estao na mesma linha
            coord1[1] += passo
            coord2[1] += passo
        elif coord1[1] == coord2[1]:
            # caso em que as duas letras estao na mesma coluna
            coord1[0] += passo
            coord2[0] += passo
        else:
            # caso em que as duas letras nao estao na mesma linha nem mesma coluna
            coord1[1], coord2[1] = coord2[1], coord1[1]
        coord1 = [retorno if x == limite else x for x in coord1]
        coord2 = [retorno if x == limite else x for x in coord2]
        return self.letter(coord1) + self.letter(coord2)

    def coords(self, pair):
        """Retorna as coordenadas de um par de letras numa matriz."""
        coords = []
        for letter in pair:
            for line in range(len(self.square)):
                if letter in self.square[line]:
                    coords.append([line, self.square[line].index(letter)])
                    break
        return coords

    def letter(self, coord):
        """Retorna a letra com a coordenada dada"""
        return self.square[coord[0]][coord[1]]

cipher.py

# -*- coding: utf-8 -*-
""" Classe com metodos basicos para cifras classicas """

class Cipher(object):
    """ Classe base para as cifras classicas """
    plain_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    plain_alphanum = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'

    def format_str(self, text):
        '''Retorna text sem espacos e em maiusculas'''
        return text.replace(' ', '').upper()

    def shift_alphabet(self, alphabet, shift):
        '''Retorna alphabet com deslocamento de valor shift'''
        return alphabet[shift:] + alphabet[:shift]

    def create_square(self, key = '', alphanum = False, replace = ['J', 'I'], sequence = False):
        """Retorna um alfabeto numa matriz de num x num
        Por padrao, retorna uma matriz formada pelo alfabeto ABCDEFGHIKLMNOPQRSTUVWXYZ
        """
        square = []
        if alphanum:
            num = 6
            alfabeto = self.plain_alphanum
            replace = ['', '']
        else:
            num = 5
            alfabeto = self.plain_alphabet
        alfabeto = self.create_alphabet(key, alfabeto, replace, sequence)
        for idx in range(0, len(alfabeto), num):
            square.append(alfabeto[idx:idx + num])
        return square

    def create_alphabet(self, key = '', alfabeto = plain_alphabet, replace = ['', ''], sequence = False):
        '''Retorna um alfabeto com key como chave e no inicio do alfabeto'''
        if key:
            key = key.upper()
            temp = ''
            for ch in key:
                if ch not in temp:
                    temp += ch
            key = temp
            if replace[0] in key:
                key = key.replace(replace[0], replace[1])
            if sequence:
                idx = alfabeto.index(key[-1])
                alfabeto = self.shift_alphabet(alfabeto, idx)
        cipher = alfabeto.replace(replace[0], '')
        for ch_key in key:
            if ch_key in cipher:
                cipher = cipher.replace(ch_key, '')
        return key + cipher

Estas cifras, bem como todas as outras estão disponíveis em meu GitHub.

TESTES

from playfair import Playfair

versao = sys.version_info[0]
 
if versao == 2:
    leitura = raw_input
elif versao == 3:
    leitura = input

txt_in = leitura('Texto a ser cifrado: ')
password = leitura('Senha: ')

cifra = Playfair()
cifrado = cifra.encrypt(txt_in, password)
print(cifrado)
print(cifra.decrypt(cifrado, password))

Cujo resultado de saída será:

Texto a ser cifrado: temos um agente infiltrado
Senha: espiao
NIGFAQUFNOQUSAUODTUTIFBV
temosumagenteinfiltradow
Fontes:
http://www.numaboa.com.br/criptografia/substituicoes/poligramicas/1041-playfair
https://en.wikipedia.org/wiki/Playfair_cipher
http://practicalcryptography.com/ciphers/playfair-cipher/
https://learncryptography.com/classical-encryption/playfair-cipher
http://crypto.interactive-maths.com/playfair-cipher.html

Sobre Fábio Medeiros

Meu nome é Fábio Medeiros. Cearense de nascença e com muito orgulho (daí o nome do blog, uma referência à minha terra). Sou formado em Tecnologia em Telemática, pelo CEFET-CE. Escrevi alguns artigos sobre programação JavaME e dispositivos portáteis (PDA) para a revista WebMobile.
Esse post foi publicado em Criptografia e marcado , . Guardar link permanente.

8 respostas para Criptografia: Cifra Playfair em Python

  1. Gregório disse:

    Consegue faze um gerador de código playfair em batch script?

    • Fábio Medeiros disse:

      Olá Gregório,

      Possível deve ser. Como não tenho prática com batch do Windows (supondo que seja este a que você se refere) levaria bastante tempo.

  2. Gabriel disse:

    Olá, como executo esses dois código? eles devem estar na mesma pasta? estou usando vs code

  3. Projeto Integrador disse:

    olá, esta dando um erro, poderia me ajudar?

Deixe um comentário