Através da criptografia obtemos diversas propriedades importantes como a confidencialidade (sigilo da informação), integridade (garantia que a mensagem não foi alterada), autenticidade (quem foi a autor da mensagem) e irretratabilidade ou não repúdio (capacidade de não negar a construção da mensagem). Temos ainda que a criptografia Simétrica garante a confidencialidade e a integridade, enquanto que a criptografia Assimétrica garante a confidencialidade, integridade, autenticidade e a irretratabilidade ou não repúdio.

Assim sendo, podemos classificar os algoritmos através do número de chaves (simétrico ou assimétrico). Nos algoritmos simétricos uma chave é usada tanto para criptografar quanto para descriptografar (podemos ter mais que uma se a segunda for facilmente derivada da primeira), enquanto que nos algoritmos assimétricos temos mais que uma chave e ambas são completamente independentes uma das outras.

Outra classificação para os algoritmos é em relação aos métodos de operação que podem ser dois: de substituição e de transposição. Mais uma importante classificação é em relação ao modo de processamento que pode ser: os cifradores de bloco e cifradores de fluxo. Cifradores de Bloco operam sobre 8 bits ou 16 bits e funcionam com complementos para que todos blocos tenham o mesmo tamanho. Cifradores de Fluxo é onde a cifragem ocorre bit a bit continuo. Essas classificações são importantes e nos permitem melhor organizar cada algoritmo criptográfico.

Nessa importante área de segurança da informação ainda temos duas ciências muito estudas: a Criptologia que se divide em Criptografia e que tem como foco o estudo de como tornar algo legível em ilegível e a Criptoanálise que estuda a arte de quebrar textos cifrados. Por fim, temos a Estagonologia que se divide em Esteganografia que estuda a ocultação da informação e Esteganoanálise que é a arte de revelar informações ocultas.

A criptografia através de cifras ocorre com a cifração da mensagem original através de operações de transposição e substituição de seus caracteres, resultando numa mensagem cifrada. Para decifração basta aplicar o processo inverso. Temos dois tipos de cifras, as cifras mono alfabéticas e as cifras poli alfabéticas. As cifras mono alfabéticas são compostas pela mais famosa de todas que é conhecida como Cifra de César onde basicamente cada letra do alfabeto é deslocada da sua posição um número fixo de lugares k, tal que 1<=k<=25 (número de letras que compõem o alfabeto). Originalmente a cifra anda três posições. Portanto, por exemplo, a palavra "senha" seria cifrada como "vhqkd", assim "s" andou três posições e foi para o "v", “e” andou mais três posições e foi para o “h” e assim por diante. A Cifra de César ainda é utilizada em algumas aplicações mais simples que necessitam de alguma criptografia. Por sua vez, as cifras poli alfabéticas são compostas pela cifra de Vigenère que consiste no uso de várias cifras de César em sequencia, com diferentes valores de deslocamento ditados por uma palavra-chave.

Os sistemas criptográficos são compostos por dois tipos: Simétricos e Assimétricos. Na próxima seção veremos mais sobre a criptografia Assimétrica estudando seus conceitos, funcionamento, algoritmos e como podemos aplica-los na prática utilizando a linguagem de programação Java que suporta amplamente a criptografia Assimétrica.

Criptografia Assimétrica

Na criptografia Assimétrica (ou criptografia de chave pública) temos que a chave de cifração é diferente da chave de decifração e uma não pode ser facilmente gerada a partir da outra. Basicamente temos que no processo de encriptação utilizaremos uma chave “k1” em cima da mensagem em texto puro que então irá gerar um texto cifrado. Após isso, no processo de descriptografia usaremos outra chave “k2” em cima do texto cifrado e teremos como resposta de volta o texto claro.

Basicamente na criptografia assimétrica temos que a chave pública pode ser conhecida por todos e é utilizada para cifrar o texto claro. Por sua vez, a chave privada deve permanecer secreta e é utilizada para decifrar o texto cifrado. Esse processo nos garante a confidencialidade da informação. Porém, também é possível utilizar a chave privada para cifrar o texto claro e a respectiva chave pública para decifrar a mensagem criptografada. Neste caso, busca-se garantir a autenticidade. É caso típico de assinaturas digitais.

Para entendermos melhor como funciona esse processo imagina que eu queira enviar uma mensagem para o meu amigo João. Neste caso, devemos cifrar a mensagem com a chave pública de João. Ao receber a mensagem cifrada, João decifra a mensagem com a sua chave privada. Podemos notar que a confidencialidade está garantida, pois somente a chave privada de João pode decifrar a mensagem criptografada e somente ele possui essa chave, não a distribuindo para ninguém. Outra forma de utilizar a criptografia assimétrica é garantindo a autenticidade. Exemplificando o processo agora podemos imaginar que eu cifre a mensagem utilizando a minha chave privada e envie essa mensagem para João, podemos verificar que agora não foi utilizada a chave pública de João e sim a minha própria chave privada que somente eu e mais ninguém tem acesso. Como somente a minha chave pública pode decifrar a mensagem João tem certeza que quem enviou a mensagem foi eu.

Nesta criptografia temos também um alto custo computacional para criptografar e descriptografar. Na maioria dos casos o tamanho da chave é grande, maior que as utilizadas na criptografia simétrica, mas nem sempre isso é verdade, depende do algoritmo utilizado.

Portanto, uma chave pública é disponibilizada gratuitamente a qualquer pessoa que queira enviar uma mensagem. Uma segunda chave privada é mantida em sigilo, para que somente a própria pessoa a tenha. Isso significa que não precisaremos se preocupar com o envio das chaves públicas na Internet. Um problema com a criptografia assimétrica, no entanto, é que ela é mais lenta do que a criptografia simétrica. Ela requer muito mais capacidade de processamento para criptografar e descriptografar o conteúdo da mensagem.

Existem diferentes algoritmos assimétricos sendo uns dos mais conhecidos o RSA que tem esse nome devido aos seus desenvolvedores Rivest, Shamir, e Adleman. Este algoritmo é amplamente utilizado nos navegadores, para sites seguros e para criptografar e-mails. O RSA será mais detalhado na próxima seção.

RSA

O RSA é o método de criptografia mais utilizado no mundo. No RSA utilizamos duas chaves, uma chave para encriptação e outra para decriptação. Ele resolve o problema de distribuição de chaves da criptografia simétrica usando envelopamento digital e a segurança é baseada na fatoração de números extensos. Quanto maior a chave maior a segurança, porém o processamento também é maior.

A construção de chaves é feita através da multiplicação de dois números primos relativamente grandes que gera um número que será elevado a um expoente que é um número público, e após isso ele é novamente elevado a outro expoente que é um número privado. Assim teremos um número público e um número privado. O processo de descriptografia (em que os números primos são novamente gerados) será revertido através de fatoração, que é o inverso da multiplicação.

O RSA foi construído sobre uma das áreas mais clássicas da matemática, a teoria dos números. Ele se baseia na dificuldade em fatorar um número em seus componentes primos (números divisíveis por 1 e por ele mesmo). Todo número inteiro positivo maior que 1 pode ser decomposto de forma única em um produto de números primos, por exemplo, 26 é um produto de 2 * 13, 44 é um produto de 2 * 2 * 11. Apesar de parecer simples fatorar esses números pequenos, a situação fica bastante complexa e demorada quando temos que fatorar números grandes não podendo ser resolvidos em um tempo polinomial determinístico. No RSA a chave pública e a chave privada são geradas com base na multiplicação de dois números primos e o resultado desta multiplicação será público, mas se o número for grande o suficiente, fatorar este número para descobrir os primos que multiplicamos para formá-lo pode demorar anos. Por isso que o RSA é seguro, sendo impossível quebrar a sua criptografia.

O método do RSA se dá primeiramente com a construção de uma tabela atribuindo a cada letra um número. Isto é necessário visto que o RSA codifica somente números. Após isso, escolhemos os números primos e quanto maior for esses números melhor. A RSA Data Security, que é responsável pela padronização do RSA, recomenda que se utilizem chaves de 2048 bits (o que resulta em um número com 617 dígitos) se quisermos garantir que a chave não seja quebrada até pelo menos o ano de 2030. Após a atribuição de números para as letras agora temos que calcular a função “totiente” que diz a quantidade de co-primos de um numero que são menores que ele mesmo. Feito isso, o próximo passo é calcular a chave pública que é onde escolhe-se um número "e" em que 1 < e < função totiente. Com a chave pública em mãos podemos agora cifrar a mensagem aplicando, para cada letra, a fórmula “c = m ^ e mod n”, onde "e" é a chave pública e "m" é o valor numérico da letra.

Para exemplificar o funcionamento do algoritmo vamos escolher inicialmente dois números primos quaisquer “P” e “Q”. Obviamente que vamos escolher dois números primos pequenos apenas por questão de exemplificação. Portanto, consideramos que “P = 17” e “Q = 11”.

Após definirmos os valores dos números primos devemos calcular dois novos números N e Z de acordo com os números P e Q escolhidos, portanto temos que:

N = P * Q

Z = (P-1)*(Q-1)

Assim, após substituir os valores teremos como resultado:

N = 17 * 11 = 187

Z = 16 * 10 = 160

O próximo passo é definir um número D que tenha a propriedade de ser primo em relação a Z. Podemos escolher qualquer número como, por exemplo, o número “7”.

Agora podemos começar o processo de criação da chave pública e da chave privada. Devemos encontrar um número E que satisfaça a propriedade "(E * D) mod Z = 1". Se tomarmos o número “1” e substituindo os valores na fórmula teremos "E = 1 => (1 * 7) mod 160 = 7" que não satisfaz a condição, pois o resultado foi “7”. Se tomarmos os números “2”, “3” até o “22” nenhum satisfará a condição, mas o número “23” satisfará resultando em "E = 23 => (23 * 7) mod 160 = 1". Outros números também satisfazem essa condição, como “183”, “343”, “503”, etc.

Dessa forma, tomamos como referência “E = 23”. Agora podemos definir as chaves de encriptação e desencriptação. Para criptografar utilizamos “E” e “N”, esse par de números é utilizado como chave pública. Para descriptografar utilizamos “D” e “N”, esse par de números é utilizado como chave privada.

Assim, temos as equações definidas abaixo:

Texto Criptografado = (Texto Puro ^ E) mod N

Texto Puro = (texto Criptografado ^ D) mod N

Como um exemplo prático vamos imaginar uma mensagem bastante simples que tem o número “4” no seu corpo e será retornada ao destinatário. Para criptografar essa mensagem teríamos o texto original como sendo “4”, o texto criptografado seria "(4 ^ 23) mod 187" que é "70.368.744.177.664 mod 187" resultando em “64”.

Para descriptografar destinatário recebe o texto “64”. Recebido o texto criptografado e aplicando a fórmula temos que o texto original será "(64 ^ 7) mod 187" ou "4.398.046.511.104 mod 187" que resulta em “4” que é o texto originalmente criado. Como o RSA trabalha com número devemos converter um alfabeto em número, assim a letra A seria 0, B seria 1, C seria 2, e assim por diante.

Podemos notar que a escolha dos números primos envolvidos é fundamental para o algoritmo, por isso escolhe-se números primos gigantes para garantir que a chave seja inquebrável.

Segue na Listagem 1 um exemplo de um algoritmo em Java para criptografar e descriptografar o texto puro utilizando o RSA.

Listagem 1. Criptografando e Descriptografando dados com RSA.


  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.ObjectInputStream;
  import java.io.ObjectOutputStream;
  import java.security.KeyPair;
  import java.security.KeyPairGenerator;
  import java.security.PrivateKey;
  import java.security.PublicKey;
  import javax.crypto.Cipher;
   
   
  public class EncriptaDecriptaRSA {
   
    public static final String ALGORITHM = "RSA";
   
    /**
     * Local da chave privada no sistema de arquivos.
     */
    public static final String PATH_CHAVE_PRIVADA = "C:/keys/private.key";
   
    /**
     * Local da chave pública no sistema de arquivos.
     */
    public static final String PATH_CHAVE_PUBLICA = "C:/keys/public.key";
   
    /**
     * Gera a chave que contém um par de chave Privada e Pública usando 1025 bytes.
     * Armazena o conjunto de chaves nos arquivos private.key e public.key
     */
    public static void geraChave() {
      try {
        final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
        keyGen.initialize(1024);
        final KeyPair key = keyGen.generateKeyPair();
   
        File chavePrivadaFile = new File(PATH_CHAVE_PRIVADA);
        File chavePublicaFile = new File(PATH_CHAVE_PUBLICA);
   
        // Cria os arquivos para armazenar a chave Privada e a chave Publica
        if (chavePrivadaFile.getParentFile() != null) {
          chavePrivadaFile.getParentFile().mkdirs();
        }
        
        chavePrivadaFile.createNewFile();
   
        if (chavePublicaFile.getParentFile() != null) {
          chavePublicaFile.getParentFile().mkdirs();
        }
        
        chavePublicaFile.createNewFile();
   
        // Salva a Chave Pública no arquivo
        ObjectOutputStream chavePublicaOS = new ObjectOutputStream(
            new FileOutputStream(chavePublicaFile));
        chavePublicaOS.writeObject(key.getPublic());
        chavePublicaOS.close();
   
        // Salva a Chave Privada no arquivo
        ObjectOutputStream chavePrivadaOS = new ObjectOutputStream(
            new FileOutputStream(chavePrivadaFile));
        chavePrivadaOS.writeObject(key.getPrivate());
        chavePrivadaOS.close();
      } catch (Exception e) {
        e.printStackTrace();
      }
   
    }
   
    /**
     * Verifica se o par de chaves Pública e Privada já foram geradas.
     */
    public static boolean verificaSeExisteChavesNoSO() {
   
      File chavePrivada = new File(PATH_CHAVE_PRIVADA);
      File chavePublica = new File(PATH_CHAVE_PUBLICA);
   
      if (chavePrivada.exists() && chavePublica.exists()) {
        return true;
      }
      
      return false;
    }
   
    /**
     * Criptografa o texto puro usando chave pública.
     */
    public static byte[] criptografa(String texto, PublicKey chave) {
      byte[] cipherText = null;
      
      try {
        final Cipher cipher = Cipher.getInstance(ALGORITHM);
        // Criptografa o texto puro usando a chave Púlica
        cipher.init(Cipher.ENCRYPT_MODE, chave);
        cipherText = cipher.doFinal(texto.getBytes());
      } catch (Exception e) {
        e.printStackTrace();
      }
      
      return cipherText;
    }
   
    /**
     * Decriptografa o texto puro usando chave privada.
     */
    public static String decriptografa(byte[] texto, PrivateKey chave) {
      byte[] dectyptedText = null;
      
      try {
        final Cipher cipher = Cipher.getInstance(ALGORITHM);
        // Decriptografa o texto puro usando a chave Privada
        cipher.init(Cipher.DECRYPT_MODE, chave);
        dectyptedText = cipher.doFinal(texto);
   
      } catch (Exception ex) {
        ex.printStackTrace();
      }
   
      return new String(dectyptedText);
    }
   
    /**
     * Testa o Algoritmo
     */
    public static void main(String[] args) {
   
      try {
   
        // Verifica se já existe um par de chaves, caso contrário gera-se as chaves..
        if (!verificaSeExisteChavesNoSO()) {
         // Método responsável por gerar um par de chaves usando o algoritmo RSA e
         // armazena as chaves nos seus respectivos arquivos.
          geraChave();
        }
   
        final String msgOriginal = "Exemplo de mensagem";
        ObjectInputStream inputStream = null;
   
        // Criptografa a Mensagem usando a Chave Pública
        inputStream = new ObjectInputStream(new FileInputStream(PATH_CHAVE_PUBLICA));
        final PublicKey chavePublica = (PublicKey) inputStream.readObject();
        final byte[] textoCriptografado = criptografa(msgOriginal, chavePublica);
   
        // Decriptografa a Mensagem usando a Chave Pirvada
        inputStream = new ObjectInputStream(new FileInputStream(PATH_CHAVE_PRIVADA));
        final PrivateKey chavePrivada = (PrivateKey) inputStream.readObject();
        final String textoPuro = decriptografa(textoCriptografado, chavePrivada);
   
        // Imprime o texto original, o texto criptografado e 
        // o texto descriptografado.
        System.out.println("Mensagem Original: " + msgOriginal);
        System.out.println("Mensagem Criptografada: " +textoCriptografado.toString());
        System.out.println("Mensagem Decriptografada: " + textoPuro);
   
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }

Se executarmos o código acima teremos como resultado:


  Mensagem Original: Exemplo de mensagem
  Mensagem Criptografada: [B@9ee4e7
  Mensagem Decriptografada: Exemplo de mensagem

No código acima utilizamos KeyPairGenerator para criar nossa chave. Segue abaixo o trecho de código:


  final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
  keyGen.initialize(1024);
  final KeyPair key = keyGen.generateKeyPair();

Primeiramente criamos uma instância de KeyPairGenerator para geração das chaves do RSA e após isso inicializamos o gerador informando o tamanho em bits do módulo. Normalmente o tamanho desse módulo é de 1024 bits ou 2048 bits por questões de desempenho. Logo após chamamos o método genKeyPair() que retornará um objeto do tipo KeyPair contendo as chaves. Por fim, chamamos getPublic() e getPrivate() para recuperarmos a chave pública e a chave privada.

Geradas as chaves já podemos armazená-las no disco para uso posterior e criptografar e descriptografar mensagens.

Bibliografia

[1] MessageDigest, disponível em http://docs.oracle.com/javase/7/docs/api/java/security/MessageDigest.html

[2] STALLINGS, William. Criptografia e segurança de redes: Princípios e práticas, 4 ed. São Paulo: Prentice Hall, 2008.