Criptografia, Hash e Assinatura Digital – Aplicações e Implicações
Muitas vezes vejo uma enorme confusão entre os termos, e a forma correta de aplica-los. Então, neste artigo, vamos explicar o significado de cada um deles, as principais aplicações e tentar desmistificar um pouco esse assunto.
Primeiro, criptografia é uma palavra que se origina do Grego, onde: Kryptós = escondido, secreto e Graphein que significa escrita, ou seja, Escrita Escondida. A origem está em ‘escrever de forma escondida’, significando a prática de técnicas para comunicações seguras. Seu primeiro uso conhecido foi em 1900 AC. Ou seja, há mais de 3.900 anos. Originalmente, era usado simples formas que podiam ser decriptadas manualmente, com papel e caneta. Porém mais recentemente, as técnicas evoluíram muito e máquinas e algorítimos foram criados especificamente para isso.
Hash é uma técnica de dispersão criptográfica, uma função hash é considerada praticamente impossível de inverter, isto é, de recriar o valor de entrada utilizando somente o valor de dispersão. Essas funções hash unidirecionais têm sido chamadas de “os operários da criptografia moderna”. Os dados de entrada costumam ser chamados de mensagem, e o valor de dispersão mensagem resumida ou simplesmente resumo. Uma função de dispersão criptográfica deve possuir quatro propriedades principais: ser fácil computar o valor de dispersão para qualquer mensagem, ser difícil gerar uma mensagem a partir de seu resumo, difícil modificar a mensagem sem modificar o resumo e difícil encontrar duas mensagens diferentes com o mesmo resumo.
Hash
Trata-se de uma função matemática aplicada sobre um conjunto de dados que gera outro número, este conhecido como hash. De forma bem simplista, pode ser equivalente a um dígito verificador. Os dígitos são calculados em função de todos os outros digítos da mensagem, de forma que qualquer alteração nos demais dados geraria um novo dígito verificador, permitindo assim que perceba-se facilmente qualquer erro de digitação. Neste caso, o hash contendo apenas um dígito apenas é bem simples e tem propósito simples também, apenas validar eventuais erros de digitação.
Claro, para que tudo isso funcione, os algoritmos de hash devem ser públicos, para que qualquer entidada possa calculá-lo e validar os dados. Os mais comuns são o MD5, SHA1, SHA-256, SHA-512. Outra característica de hashes é que cada algoritmo sempre gerará o mesmo hash para a mesma entrada, e não é possível calcular a entrada a partir do hash.
Seu principal uso em sistemas, é por exemplo, validar se os dados de uma mensagem (que pode estar criptografada ou não) são válidos, já que diferentes conteúdos geram hash’s diferentes. Guardar senhas em bancos de dados, verificar a digitação de sequencias de dados, entre muitas outras.
Propriedades
Muitas funções hash criptográficas são projetadas para receber uma cadeia de caracteres de qualquer tamanho como entrada e produzir um valor hash de tamanho fixo. Uma função hash deve ser capaz de aguentar todo tipo de ataque cripto-analítico conhecido.
Verificação de senha é uma aplicação relacionada ao uso de hash. Armazenar todas as senhas de um usuário como puro texto (da forma que são digitadas) pode resultar em uma quebra massiva de segurança caso o arquivo de senhas seja comprometido. Uma maneira de reduzir esse perigo é de apenas armazenar o resumo de cada senha (HASH). Para autenticar um usuário, calcula-se o resumo da senha fornecida pelo usuário e o compara-se ao resumo armazenado. (Note que essa abordagem impede que a senha original seja recuperada se esquecida ou perdida, e terá que ser substituída por uma nova.) A senha, geralmente, é concatenada com um valor aleatório, não-secreto conhecido como sal, antes de ser aplicada a função hash. O sal é armazenado junto ao hash da senha. Como usuários devem possuir sais diferentes, é impraticável armazenar tabelas de valores hash pré-computados para senhas comuns. Funções de alongamento de chave, como PBKDF2, Bcrypt ou Scrypt, costumam utilizar chamadas repetidas de hash criptográficos para aumentar o tempo necessário de um ataque de força bruta sobre o resumo das senhas armazenadas.
Assinatura Digital
A assinatura digital é um hash mais elaborado. Também consiste de uma função matemática aplicada sobre os dados de entrada – um arquivo, uma mensagem de texto, etc. Porém neste caso além de garantir que o conteúdo não foi alterado, também queremos garantir a autenticidade de quem o gerou. Uma aplicação bem comum de assinatura é o envio de dados para a Receita Federal. Neste caso podemos utilizar um certificado digital para garantir que sua declaração de imposto de renda não foi alterada na transmissão e que não foi gerada por outro contribuinte tentando se passar por você.
Para que isso funcione, o cálculo da assinatura deve levar em consideração não só os dados, mas também uma informação que identifique o emissor. Poderíamos usar o CPF, pois ele identifica unicamente o contribuinte, então? Não, pois o CPF é uma informação facilmente obtida e assim qualquer pessoa poderia se passar por outra. Para que o processo funcione, é necessário que a informação seja conhecida apenas pelo emissor. O receptor, por sua vez, precisa conferir que os dados recebidos são autênticos, validando a assinatura. Mas como ele não conhece a chave usada na assinatura, não pode regerar a assinatura para validar.
Neste caso, o processo precisa ser mais avançado. O emissor deve gerar 2 informações, conhecidas como chave privada e chave pública. A chave privada, como o nome induz, é de conhecimento apenas do emissor da mensagem, enquanto a chave pública pode ser divulgada. As chaves possuem uma relação matemática muito forte, de tal forma que o receptor da mensagem pode conferir que a mesma foi de fato emitida pela pessoa esperada usando apenas a chave pública.
O algoritmo mais conhecido para geração deste par de chaves é o RSA, também de domínio público. E para que não haja fraudes, existe todo um processo, com envio de diversos documentos comprobatórios de identidade para autoridades certificadoras.
Criptografia
Até o momento falamos de processos para garantir a integridade e autenticidade de uma informação qualquer, mas nada que garanta a confidencialidade da informação transmitida. Qualquer entidade que intercepte a comunicação poderia ter acesso ao conteúdo da mensagem. Isto, claro, não é adequado em um ambiente onde são trafegados dados estratégicos e confidenciais.
Para resolver este cenário, surge o conceito de criptografia, que consiste em uma função que transforma uma mensagem legível em outra ilegível para transmissão. Você possivelmente já criptografou mensagens na infância, sem mesmo ter conhecimento deste termo, quando trocou cada letra por outra para embaralhar um bilhete antes de enviá-lo a outra pessoa. Diga-se de passagem, essa técnica foi muito usada na Roma Antiga, pelo imperador César e hoje é conhecida como chave de Cézar. Apesar se muito simples, trata-se de um algoritmo de criptografia adequado às suas necessidades e recursos computacionais da época.
Durante a Segunda Guerra Mundial, processo semelhante foi usado em transmissões via rádio para evitar que as mensagens pudessem ser interceptadas. Cada letra era substituída por outra, porém desta vez o hardware utilizado para criptografia possuía uma configuração que alterava o resultado do processo. A primeira máquina que foi criada especificamente para essa finalidade foi chamada pelos alemães de ENIGMA.
Perceba que em ambos os casos temos os dados de entrada, os dados de saída criptografados, um algoritmo bem definido (trocar uma letra por outra), e uma chave. No caso do bilhete, a chave é tabela de-para com as letras. Já na segunda guerra , a chave era a configuração do hardware. E a chave usada na criptografia também era usada na descriptografia. Este tipo de criptografia é conhecida como criptografia de chave simétrica e hoje existem algoritmos bem sofisticados para o processo, como AES, DES e Triple DES.
Existe ainda um outro tipo de criptografia – assimétrica – onde as chaves usadas para criptografar e descriptografar são diferentes. Similar ao processo de assinatura, temos uma função matemática que criptografa a mensagem. Porém neste caso os papeis das chaves se invertem. A mensagem é criptografada com a chave pública do destinatário – para que qualquer entidade possa enviar mensagens criptografadas ao mesmo – e descriptografada com a chave privada do mesmo – para que apenas ele possa abrir a mensagem. A relação matemática entre as chaves precisa ser bem definida para que se possa criptografar a mensagem sem o conhecimento da chave que irá descriptografá-la. Aqui também o algoritmo mais conhecido é o RSA, e normalmente existe todo um processo para emissão das chaves.
Boas Práticas
Atualmente é difícil pensar em um sistema ou aplicativo que não utilize um login e uma senha, e nem sempre podemos utilizar sistemas externos de autenticação como o Google Auth ou a API do facebook ou qualquer outro, seja por politicas internas do cliente ou por limitações como uma rede local sem acesso externo. Porém, mesmo nestes casos, existe o risco de um funcionário mal intencionado explorar falhas em sistemas de login.
Não é incomum, ver sistemas que não protegem os dados das senhas dos usuários, salvando as senhas numa tabela de banco de dados, sem nenhum tipo de criptografia, para esta função, é que a maioria das linguagens e sistemas de bancos de dados possuem compatibilidade com funções hash.
Uma função que gera o hash recebe como entrada um texto qualquer, e o mapeia para uma cadeia de caracteres com tamanho fixo. Por exemplo:
Hash de “minha_senha_segura” = 4f24ed619ffb73b8792e3f74e5ffa3bb;
Hash de “sp23494” = de1dc74c5561739cbe4c6368f78e9b6f;
A partir de de1dc74c5561739cbe4c6368f78e9b6f é inviável descobrir a senha original!
Mas, peraí, o usuário ao fazer login não vai digitar esse código gigante, né? Na verdade, quando recebemos uma requisição de autenticação, o código da aplicação irá calcular o hash da senha informada e comparar com o que está no banco de dados! Desta forma, iremos comparar hashes e não mais senhas brutas, nos poupando do problema de guardar esta informação sensível no banco de dados.
Isso é no mínimo da segurança, mais ainda assim, na internet temos centenas de BANCOS DE HASH ou seja, sistemas que possuem armazenados hashs para as senhas mais comuns, então, só transformar a senha em hash não é o bastante. Devemos concatenar a senha a alguma outra informação, que geralmente é uma string única com pelo menos 13 caracteres, ao qual damos o nome de SALT.
Vamos a um exemplo passo a passo:
1-Nossa tela de login, pede o usuário, e a senha
2-Via https, nosso front-end envia o login (que pode ser o email) e um hash por exemplo MD5 da senha.
3-Nosso back-end recebe o hash e o concatena ao nosso SALT e gera outro hash por exemplo um SHA256 dos dados concatenados.
4-Os dados salvos no BANCO DE DADOS, são hash de um hash+salt o que garante uma maior segurança.
Vamos agora usar isso, com os dados:
No caso, nosso usuário seria LUANA e a senha 123456 o que é bastante inseguro – o hash MD5(123456) é e10adc3949ba59abbe56e057f20f883e e essa string é passada para nosso sistema, no sistema temos uma CONSTANTE que é definida como sendo nosso SALT, neste caso usamos o valor c6a279c3-9106-4d0c-9c3e-3ec7c3a71a82 que foi gerado usando as diretrizes da RFC-4122 que pode ser lida aqui e é praticamente único e difícil de ser previsto. Na nossa tabela de banco de dados, temos armazenado o valor hash de sha256(e10adc3949ba59abbe56e057f20f883e.c6a279c3-9106-4d0c-9c3e-3ec7c3a71a82) que é 9f95fe3c5de1449806db25d77e069222bb550ae16fc94f7a6d4c144d34b494d4 o que torna praticamente inquebravel, já que as funções HASH não podem ser decriptadas.