Criptografia em Java

Olá Habr! Apresento a você a tradução do artigo "Java Cryptography" de Jakob Jenkov.


Esta publicação é uma tradução do primeiro artigo sobre Java Cryptography de uma série de artigos para iniciantes que desejam aprender o básico da criptografia em Java.


Sumário:


  1. Criptografia Java
  2. Cifra
  3. Messagedigest
  4. Mac
  5. Assinatura
  6. Par de chaves
  7. Keygenerator
  8. KeyPairGenerator
  9. Keystore
  10. Keytool
  11. Certificado
  12. CertificateFactory
  13. CertPath

Criptografia Java


A API Java Cryptography oferece a capacidade de criptografar e descriptografar dados em java, além de gerenciar chaves, assinaturas e autenticar (autenticar) mensagens, calcular hashes criptográficos e muito mais.


Este artigo explica os conceitos básicos de como usar a API Java Cryptography para executar várias tarefas que exigem criptografia segura.


Este artigo não explica os conceitos básicos da teoria criptográfica. Você precisará ver essas informações em outro lugar.


Extensão de criptografia Java


A API de criptografia Java é fornecida pelo chamado Java Cryptography Extension (JCE). A JCE faz parte da plataforma Java há muito tempo. Inicialmente, o JCE foi separado do Java devido a restrições de exportação na tecnologia de criptografia nos Estados Unidos. Portanto, os algoritmos de criptografia mais fortes não foram incluídos na plataforma Java padrão. Esses algoritmos de criptografia mais robustos podem ser aplicados se sua empresa estiver localizada nos EUA, mas em outros casos você terá que usar algoritmos mais fracos ou implementar seus próprios algoritmos de criptografia e conectá-los ao JCE.


Desde 2017, as regras para exportar algoritmos de criptografia nos Estados Unidos foram significativamente relaxadas e, na maior parte do mundo, você pode usar padrões internacionais de criptografia por meio do Java JCE.


Arquitetura de criptografia Java


Java Cryptography Architecture (JCA) é o nome do design da API de criptografia interna em Java. O JCA está estruturado em várias classes principais e interfaces de uso geral. A funcionalidade real dessas interfaces é fornecida pelos fornecedores. Portanto, você pode usar a classe Cipher para criptografar e descriptografar alguns dados, mas a implementação específica da cifra (algoritmo de criptografia) depende do provedor específico usado.


Você também pode implementar e conectar seus próprios provedores, mas deve ter cuidado com isso. Implementar corretamente a criptografia sem falhas de segurança é difícil! Se você não sabe o que está fazendo, provavelmente é melhor usar o provedor Java incorporado ou um provedor confiável, como o Bouncy Castle.


Principais classes e interfaces


A API Java Cryptography consiste nos seguintes pacotes Java:


  • java.security
  • java.security.cert
  • java.security.spec
  • java.security.interfaces
  • javax.crypto
  • javax.crypto.spec
  • javax.crypto.interfaces

As principais classes e interfaces desses pacotes:


  • Fornecedor
  • SecureRandom
  • Cifra
  • Messagedigest
  • Assinatura
  • Mac
  • AlgorithmParameters
  • AlgorithmParameterGenerator
  • Keyfactory
  • SecretKeyFactory
  • KeyPairGenerator
  • Keygenerator
  • Keyagreement
  • Keystore
  • CertificateFactory
  • CertPathBuilder
  • CertPathValidator
  • CertStore

Fornecedor


A classe Provider (java.security.Provider) é a classe central na API de criptografia Java. Para usar a API criptográfica Java, você precisa instalar um provedor de criptografia. O Java SDK vem com seu próprio provedor de criptografia. A menos que você defina explicitamente o provedor de criptografia, o provedor padrão será usado. No entanto, esse provedor de criptografia pode não suportar os algoritmos de criptografia que você deseja usar. Portanto, pode ser necessário instalar seu próprio provedor de criptografia.


Um dos provedores de criptografia mais populares para a API de criptografia Java é chamado Bouncy Castle. Aqui está um exemplo em que BouncyCastleProvider é definido como o provedor de criptografia:


import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public class ProviderExample { public static void main(String[] args) { Security.addProvider(new BouncyCastleProvider()); } } 

Cifra


A classe Cipher (javax.crypto.Cipher) representa um algoritmo criptográfico. Uma cifra pode ser usada tanto para criptografia quanto para decriptografar dados. A classe Cipher é explicada em mais detalhes nas seções a seguir, com uma breve descrição abaixo.


Criando uma instância da classe de cifra que usa o algoritmo de criptografia AES para uso interno:


 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 

O método Cipher.getInstance (...) aceita uma string que determina qual algoritmo de criptografia usar, bem como alguns outros parâmetros do algoritmo.
No exemplo acima:


  • AES - algoritmo de criptografia
  • CBC é um modo no qual o algoritmo AES pode funcionar.
  • PKCS5Padding é como o algoritmo AES deve processar os últimos bytes de dados para criptografia. O que exatamente isso significa, veja no manual de criptografia como um todo, e não neste artigo.

Inicialização de cifra


Antes de usar uma instância de cifra, você deve inicializá-la. A instância de cifra é inicializada chamando o método init () . O método init () usa dois parâmetros:


  • Modo - Criptografia / Descriptografia
  • Key

O primeiro parâmetro indica o modo de operação da instância de criptografia: para criptografar ou descriptografar dados. O segundo parâmetro indica qual chave eles usam para criptografar ou descriptografar dados.


Um exemplo:


 byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); cipher.init(Cipher.ENCRYPT_MODE, key); 

Observe que o método de criação de chave neste exemplo é inseguro e não deve ser usado na prática. Este artigo nas seções a seguir explica como criar chaves com mais segurança.


Para inicializar uma instância de cifra para descriptografar dados, você deve usar Cipher.DECRYPT_MODE, por exemplo:


 cipher.init(Cipher.DECRYPT_MODE, key); 

Criptografia ou descriptografia de dados


Após inicializar a cifra, você pode começar a criptografar ou descriptografar os dados chamando os métodos update () ou doFinal () . O método update () é usado se você estiver criptografando ou descriptografando uma parte dos dados. O método doFinal () é chamado quando você criptografa a última parte de dados ou se o bloco de dados que você passa para doFinal () é um único conjunto de dados para criptografia.


Um exemplo de criptografia de dados usando o método doFinal () :


 byte[] plainText = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8"); byte[] cipherText = cipher.doFinal(plainText); 

Para descriptografar os dados, você precisa passar o texto cifrado (dados) para o método doFinal () ou doUpdate () .


Chaves


Para criptografar ou descriptografar dados, você precisa de uma chave. Existem dois tipos de chaves, dependendo do tipo de algoritmo de criptografia usado:


  • Chaves simétricas
  • Chaves assimétricas

Chaves simétricas são usadas para algoritmos de criptografia simétrica. Os algoritmos de criptografia simétrica usam a mesma chave para criptografia e descriptografia.
Chaves assimétricas são usadas para algoritmos de criptografia assimétrica. Os algoritmos de criptografia assimétrica usam uma chave para criptografia e outra para descriptografia. Os algoritmos de criptografia de chave pública e privada são exemplos de algoritmos de criptografia assimétrica.


De alguma forma, a parte que precisa descriptografar os dados deve conhecer a chave necessária para descriptografar os dados. Se o decodificador não for parte da criptografia de dados, as duas partes deverão concordar com uma chave ou trocar uma chave. Isso é chamado de troca de chaves.


Key Security


As chaves devem ser difíceis de adivinhar para que um invasor não possa pegar facilmente uma chave de criptografia. No exemplo da seção anterior da classe Cipher, uma chave muito simples e codificada foi usada. Na prática, isso não vale a pena. Se a chave das partes for fácil de adivinhar, será fácil para um invasor descriptografar os dados criptografados e, possivelmente, criar mensagens falsas por conta própria. É importante fazer uma chave difícil de adivinhar. Assim, a chave deve consistir em bytes aleatórios. Quanto mais bytes aleatórios, mais difícil é adivinhar, porque existem mais combinações possíveis.


Geração de chaves


Para gerar chaves de criptografia aleatória, você pode usar a classe Java KeyGenerator. O KeyGenerator será descrito em mais detalhes nos próximos capítulos. Aqui está um pequeno exemplo de seu uso aqui:


 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = new SecureRandom(); int keyBitSize = 256; keyGenerator.init(keyBitSize, secureRandom); SecretKey secretKey = keyGenerator.generateKey(); 

A instância SecretKey resultante pode ser passada para o método Cipher.init () , por exemplo:


 cipher.init(Cipher.ENCRYPT_MODE, secretKey); 

Geração de par de chaves


Os algoritmos de criptografia assimétrica usam um par de chaves que consiste em uma chave pública e uma chave privada para criptografar e descriptografar dados. Para criar um par de chaves assimétrico, você pode usar KeyPairGenerator (java.security.KeyPairGenerator). O KeyPairGenerator será descrito em mais detalhes nos capítulos a seguir. Abaixo está um exemplo simples do uso do Java KeyPairGenerator:


 SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair(); 

Key Store


Java KeyStore é um banco de dados que pode conter chaves. Java KeyStore é representado pela classe KeyStore (java.security.KeyStore). Um keystore pode conter chaves dos seguintes tipos:


  • Chaves privadas
  • Chaves e certificados públicos (Chaves públicas + certificados)
  • Chaves secretas

Chaves privadas e públicas são usadas na criptografia assimétrica. A chave pública pode ter um certificado associado. Um certificado é um documento que comprova a identidade de uma pessoa, organização ou dispositivo que afirma possuir uma chave pública.


O certificado geralmente é assinado digitalmente pela parte confiável como prova.
As chaves privadas são usadas na criptografia simétrica.A classe KeyStore é bastante complexa, e é por isso que é descrita em mais detalhes posteriormente em um capítulo separado no Java KeyStore.


Ferramenta de Gerenciamento de Chaves (Keytool)


O Java Keytool é uma ferramenta de linha de comando que pode trabalhar com arquivos Java KeyStore. O Keytool pode gerar pares de chaves em um arquivo KeyStore, exportar certificados e importar certificados para o KeyStore e algumas outras funções. O Keytool vem com uma instalação Java. O Keytool é descrito em mais detalhes posteriormente em um capítulo separado sobre o Java Keytool.


Resumo da mensagem


Quando você recebe dados criptografados do outro lado, pode ter certeza de que ninguém alterou os dados criptografados no caminho para você?


Normalmente, a solução é calcular o resumo da mensagem a partir dos dados antes de criptografá-los, criptografar os dados e o resumo da mensagem e enviá-los pela rede. Um resumo da mensagem é um valor de hash calculado com base nos dados da mensagem. Se pelo menos um byte for alterado nos dados criptografados, o resumo da mensagem calculado a partir dos dados também será alterado.


Ao receber dados criptografados, você os descriptografa, calcula o resumo da mensagem a partir deles e compara o resumo da mensagem calculado com o resumo da mensagem enviada junto com os dados criptografados. Se os dois resumos de mensagens forem os mesmos, há uma alta probabilidade (mas não 100%) de que os dados não foram alterados.


O Java MessageDigest (java.security.MessageDigest) pode ser usado para calcular resumos de mensagens. Para criar uma instância do MessageDigest, o método MessageDigest.getInstance () é chamado. Existem vários algoritmos diferentes de compilação de mensagens. Você precisa especificar qual algoritmo deseja usar ao criar a instância MessageDigest. O trabalho com o MessageDigest será descrito em mais detalhes no capítulo Java MessageDigest.


Uma breve introdução à classe MessageDigest:


 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); 

Este exemplo cria uma instância do MessageDigest que usa o algoritmo de hash criptográfico interno SHA-256 para calcular os resumos das mensagens.


Para calcular o resumo da mensagem de alguns dados, chame o método update () ou digest () . O método update () pode ser chamado várias vezes e o resumo da mensagem é atualizado dentro do objeto. Quando você passar todos os dados que deseja incluir no resumo da mensagem, chame digest () e recupere o resumo do resumo da mensagem.


Um exemplo de chamada de update () várias vezes, seguida por uma chamada para digest () :


 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); messageDigest.update(data1); messageDigest.update(data2); byte[] digest = messageDigest.digest(); 

Você também pode chamar digest () uma vez, passando todos os dados para calcular o resumo da mensagem. Um exemplo:


 MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); byte[] data1 = "0123456789".getBytes("UTF-8"); byte[] digest = messageDigest.digest(data1); 

Código de autenticação de mensagem (MAC)


A classe Java Mac é usada para criar um MAC (código de autenticação de mensagens) a partir de uma mensagem. O MAC é semelhante a um resumo da mensagem, mas usa uma chave adicional para criptografar o resumo da mensagem. Tendo apenas os dados de origem e a chave, você pode verificar o MAC. Assim, um MAC é uma maneira mais segura de proteger um bloco de dados contra modificação do que um resumo da mensagem. A classe Mac é descrita em mais detalhes no capítulo Java Mac, seguido de uma breve introdução.


Uma instância do Java Mac é criada chamando o método Mac.getInstance () , passando o nome do algoritmo para uso como parâmetro. Aqui está o que parece:


 Mac mac = Mac.getInstance("HmacSHA256"); 

Antes de criar um MAC a partir dos dados, você deve inicializar a instância do Mac com a chave Aqui está um exemplo de como inicializar uma instância do Mac com uma chave:


 byte[] keyBytes = new byte[]{0,1,2,3,4,5,6,7,8 ,9,10,11,12,13,14,15}; String algorithm = "RawBytes"; SecretKeySpec key = new SecretKeySpec(keyBytes, algorithm); mac.init(key); 

Após inicializar a instância do Mac, você pode calcular o MAC a partir dos dados chamando os métodos update () e doFinal () . Se você tiver todos os dados para calcular o MAC, poderá chamar imediatamente o método doFinal () . Aqui está o que parece:


 byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); byte[] data2 = "0123456789".getBytes("UTF-8"); mac.update(data); mac.update(data2); byte[] macBytes = mac.doFinal(); 

Assinatura


A classe Signature (java.security.Signature) é usada para assinar dados digitalmente. Quando os dados são assinados, uma assinatura digital é criada a partir desses dados. Assim, a assinatura é separada dos dados.


Uma assinatura digital é criada criando um resumo da mensagem (hash) a partir dos dados e criptografando esse resumo com a chave privada do dispositivo, pessoa ou organização que deve assinar os dados. O resumo da mensagem criptografada é chamado de assinatura digital.


Para criar uma instância de Signature, o método Signature.getInstance (...) é chamado:


 Signature signature = Signature.getInstance("SHA256WithDSA"); 

Assinatura de dados


Para assinar dados, você deve inicializar a instância de assinatura no modo de assinatura chamando o método initSign (...), passando a chave privada para assinar os dados. Um exemplo de inicialização de uma instância de assinatura no modo de assinatura:


 signature.initSign(keyPair.getPrivate(), secureRandom); 

Depois de inicializar a instância de assinatura, ela pode ser usada para assinar os dados. Isso é feito chamando o método update (), passando os dados da assinatura como um parâmetro. Você pode chamar o método update () várias vezes para complementar os dados para criar a assinatura. Depois que todos os dados são passados ​​para o método update (), o método sign () é chamado para obter uma assinatura digital. Aqui está o que parece:


 byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign(); 

Verificação de Assinatura


Para verificar a assinatura, você precisa inicializar a instância de assinatura no modo de verificação chamando o método initVerify (...) , passando a chave pública como um parâmetro, usado para verificar a assinatura. Um exemplo de inicialização de uma instância de assinatura no modo de verificação é semelhante a este:


 Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initVerify(keyPair.getPublic()); 

Após a inicialização no modo de verificação, os dados assinados são transmitidos para o método update () . Uma chamada para o método confirm () retorna true ou false dependendo se a assinatura pode ser verificada ou não. Aqui está a verificação da assinatura:


 byte[] data2 = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature2.update(data2); boolean verified = signature2.verify(digitalSignature); 

Exemplo completo de assinatura e verificação


 SecureRandom secureRandom = new SecureRandom(); KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA"); KeyPair keyPair = keyPairGenerator.generateKeyPair(); Signature signature = Signature.getInstance("SHA256WithDSA"); signature.initSign(keyPair.getPrivate(), secureRandom); byte[] data = "abcdefghijklmnopqrstuvxyz".getBytes("UTF-8"); signature.update(data); byte[] digitalSignature = signature.sign(); Signature signature2 = Signature.getInstance("SHA256WithDSA"); signature2.initVerify(keyPair.getPublic()); signature2.update(data); boolean verified = signature2.verify(digitalSignature); System.out.println("verified = " + verified); 

Source: https://habr.com/ru/post/pt444764/


All Articles