Encriptação e desencriptação Java AES

author image
Por mkyong | Última actualização: 2 de Junho de 2020
vista: 64,491 | +1,553 pv/w

Criptação AES

O Padrão Avançado de Encriptação (AES, Rijndael) é um algoritmo de encriptação e desencriptação em cifra de bloco, o algoritmo de encriptação mais utilizado no mundo inteiro. O AES processa bloco de 128 bits usando uma chave secreta de 128, 192, ou 256 bits.

Este artigo mostra alguns exemplos de encriptação e desencriptação AES Java:

  • AES Encriptação de strings – (encriptar e desencriptar uma string).
  • AES Encriptação baseada em palavra-passe – (A chave secreta derivará de uma dada palavra-passe).
  • AES Encriptação de ficheiros. (baseada em palavra-passe).

Neste artigo, concentramo-nos na encriptação AES de 256-bit com o Modo Contador Galois (GCM).

GCM = CTR + Authentication.

Outra Leitura
Leia isto – NIST – Recomendação para o Modo Galois/Counter Mode (GCM)

Não utilize o Modo AES Electronic codebook (ECB)
O Modo AES ECB, ou AES/ECB/PKCS5Padding (em Java) não é semanticamente seguro – O texto criptografado do BCE pode vazar informação sobre o texto da placa. Aqui está uma discussão sobre Porque não devo utilizar a encriptação ECB?

Java e entradas de encriptação AES.

Na encriptação e descriptação AES, precisamos das seguintes entradas:

AES melhor prática de encriptação
Não reutilizar IV com a mesma chave.

1.1 O IV (valor inicial ou vector inicial), são bytes aleatórios, tipicamente 12 bytes ou 16 bytes. Em Java, podemos usar SecureRandom para gerar o IV aleatório.

 // 16 bytes IV public static byte getRandomNonce() { byte nonce = new byte; new SecureRandom().nextBytes(nonce); return nonce; } // 12 bytes IV public static byte getRandomNonce() { byte nonce = new byte; new SecureRandom().nextBytes(nonce); return nonce; }

1.2 A chave secreta AES, ou AES-128 ou AES-256. Em Java, podemos usar KeyGenerator para gerar a chave secreta AES.

 // 256 bits AES secret key public static SecretKey getAESKey() throws NoSuchAlgorithmException { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(256, SecureRandom.getInstanceStrong()); return keyGen.generateKey(); }

1.3 A chave secreta AES que derivou de uma dada palavra-passe. Em Java, podemos usar o SecretKeyFactory e PBKDF2WithHmacSHA256 para gerar uma chave AES a partir de uma dada palavra-passe.

 // AES key derived from a password public static SecretKey getAESKeyFromPassword(char password, byte salt) throws NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); // iterationCount = 65536 // keyLength = 256 KeySpec spec = new PBEKeySpec(password, salt, 65536, 256); SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); return secret; }

Usamos salt para proteger ataques do arco-íris, e é também um byte aleatório, podemos usar o mesmo 1.1 getRandomNonce para o gerar.

1.4 Agrupamos os métodos acima num único util classe, para não repetirmos o mesmo código repetidamente.

CryptoUtils.java
package com.mkyong.crypto.utils;import javax.crypto.KeyGenerator;import javax.crypto.SecretKey;import javax.crypto.SecretKeyFactory;import javax.crypto.spec.PBEKeySpec;import javax.crypto.spec.SecretKeySpec;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import java.security.spec.InvalidKeySpecException;import java.security.spec.KeySpec;import java.util.ArrayList;import java.util.List;public class CryptoUtils { public static byte getRandomNonce(int numBytes) { byte nonce = new byte; new SecureRandom().nextBytes(nonce); return nonce; } // AES secret key public static SecretKey getAESKey(int keysize) throws NoSuchAlgorithmException { KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(keysize, SecureRandom.getInstanceStrong()); return keyGen.generateKey(); } // Password derived AES 256 bits secret key public static SecretKey getAESKeyFromPassword(char password, byte salt) throws NoSuchAlgorithmException, InvalidKeySpecException { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); // iterationCount = 65536 // keyLength = 256 KeySpec spec = new PBEKeySpec(password, salt, 65536, 256); SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); return secret; } // hex representation public static String hex(byte bytes) { StringBuilder result = new StringBuilder(); for (byte b : bytes) { result.append(String.format("%02x", b)); } return result.toString(); } // print hex with block size split public static String hexWithBlockSize(byte bytes, int blockSize) { String hex = hex(bytes); // one hex = 2 chars blockSize = blockSize * 2; // better idea how to print this? List<String> result = new ArrayList<>(); int index = 0; while (index < hex.length()) { result.add(hex.substring(index, Math.min(index + blockSize, hex.length()))); index += blockSize; } return result.toString(); }}

Criptografia e decifragem AES.

A AES-GSM é a cifra autenticada mais amplamente utilizada. Este exemplo irá encriptar e desencriptar uma cadeia usando AES de 256 bits em Galois Counter Mode (GCM).

As entradas AES-GCM:

  • AES Chave secreta (256 bits)
  • IV – 96 bits (12 bytes)
  • Comprimento (em bits) da etiqueta de autenticação – 128 bits (16 bytes)

2.1 Em Java, usamos AES/GCM/NoPadding para representar o algoritmo AES-GCM. Para a saída encriptada, prefixamos os 16 bytes IV ao texto encriptado (ciphertext), porque precisamos do mesmo IV para decifrar.

Isto está bem se o IV for conhecido publicamente?
Não há problema se o IV for conhecido publicamente, o único segredo é a chave, mantê-la privada e confidencial.

Este exemplo utilizará AES para encriptar um texto simples Hello World AES-GCM e mais tarde descodificá-lo de volta ao texto simples original.

EncryptorAesGcm.java
package com.mkyong.crypto.encryptor;import com.mkyong.crypto.utils.CryptoUtils;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.spec.GCMParameterSpec;import java.nio.ByteBuffer;import java.nio.charset.Charset;import java.nio.charset.StandardCharsets;/** * AES-GCM inputs - 12 bytes IV, need the same IV and secret keys for encryption and decryption. * <p> * The output consist of iv, encrypted content, and auth tag in the following format: * output = byte {i i i c c c c c c ...} * <p> * i = IV bytes * c = content bytes (encrypted content, auth tag) */public class EncryptorAesGcm { private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; private static final int TAG_LENGTH_BIT = 128; private static final int IV_LENGTH_BYTE = 12; private static final int AES_KEY_BIT = 256; private static final Charset UTF_8 = StandardCharsets.UTF_8; // AES-GCM needs GCMParameterSpec public static byte encrypt(byte pText, SecretKey secret, byte iv) throws Exception { Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); cipher.init(Cipher.ENCRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte encryptedText = cipher.doFinal(pText); return encryptedText; } // prefix IV length + IV bytes to cipher text public static byte encryptWithPrefixIV(byte pText, SecretKey secret, byte iv) throws Exception { byte cipherText = encrypt(pText, secret, iv); byte cipherTextWithIv = ByteBuffer.allocate(iv.length + cipherText.length) .put(iv) .put(cipherText) .array(); return cipherTextWithIv; } public static String decrypt(byte cText, SecretKey secret, byte iv) throws Exception { Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte plainText = cipher.doFinal(cText); return new String(plainText, UTF_8); } public static String decryptWithPrefixIV(byte cText, SecretKey secret) throws Exception { ByteBuffer bb = ByteBuffer.wrap(cText); byte iv = new byte; bb.get(iv); //bb.get(iv, 0, iv.length); byte cipherText = new byte; bb.get(cipherText); String plainText = decrypt(cipherText, secret, iv); return plainText; } public static void main(String args) throws Exception { String OUTPUT_FORMAT = "%-30s:%s"; String pText = "Hello World AES-GCM, Welcome to Cryptography!"; // encrypt and decrypt need the same key. // get AES 256 bits (32 bytes) key SecretKey secretKey = CryptoUtils.getAESKey(AES_KEY_BIT); // encrypt and decrypt need the same IV. // AES-GCM needs IV 96-bit (12 bytes) byte iv = CryptoUtils.getRandomNonce(IV_LENGTH_BYTE); byte encryptedText = EncryptorAesGcm.encryptWithPrefixIV(pText.getBytes(UTF_8), secretKey, iv); System.out.println("\n------ AES GCM Encryption ------"); System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText)); System.out.println(String.format(OUTPUT_FORMAT, "Key (hex)", CryptoUtils.hex(secretKey.getEncoded()))); System.out.println(String.format(OUTPUT_FORMAT, "IV (hex)", CryptoUtils.hex(iv))); System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (hex) ", CryptoUtils.hex(encryptedText))); System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (hex) (block = 16)", CryptoUtils.hexWithBlockSize(encryptedText, 16))); System.out.println("\n------ AES GCM Decryption ------"); System.out.println(String.format(OUTPUT_FORMAT, "Input (hex)", CryptoUtils.hex(encryptedText))); System.out.println(String.format(OUTPUT_FORMAT, "Input (hex) (block = 16)", CryptoUtils.hexWithBlockSize(encryptedText, 16))); System.out.println(String.format(OUTPUT_FORMAT, "Key (hex)", CryptoUtils.hex(secretKey.getEncoded()))); String decryptedText = EncryptorAesGcm.decryptWithPrefixIV(encryptedText, secretKey); System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText)); }}

Encriptação

p>P>Texto Flanco :Hello World AES-GCM

Terminal
------ AES GCM Encryption ------Input (plain text) :Hello World AES-GCMKey (hex) :603d87185bf855532f14a77a91ec7b025c004bf664e9f5c6e95613ee9577f436IV (hex) :bdb271ce5235996a0709e09cEncrypted (hex) :bdb271ce5235996a0709e09c2d03eefe319e9329768724755c56291aecaef88cd1e6bdf72b8c7b54d75a94e66b0cd3Encrypted (hex) (block = 16) :------ AES GCM Decryption ------Input (hex) :bdb271ce5235996a0709e09c2d03eefe319e9329768724755c56291aecaef88cd1e6bdf72b8c7b54d75a94e66b0cd3Input (hex) (block = 16) :Key (hex) :603d87185bf855532f14a77a91ec7b025c004bf664e9f5c6e95613ee9577f436Decrypted (plain text) :Hello World AES-GCM

Texto Flanco : Hello World AES-GCM, Welcome to Cryptography!

Terminal
------ AES GCM Encryption ------Input (plain text) :Hello World AES-GCM, Welcome to Cryptography!Key (hex) :ddc24663d104e1c2f81f11aef98156503dafdc435f81e3ac3d705015ebab095cIV (hex) :b05d6aedf023f73b9e1e2d11Encrypted (hex) :b05d6aedf023f73b9e1e2d11f6f5137d971aea8c5cdd5b045e0960eb4408e0ee4635cccc2dfeec2c13a89bd400f659be82dc2329e9c36e3b032f38bd42296a8495ac840b0625c097d9Encrypted (hex) (block = 16) :------ AES GCM Decryption ------Input (hex) :b05d6aedf023f73b9e1e2d11f6f5137d971aea8c5cdd5b045e0960eb4408e0ee4635cccc2dfeec2c13a89bd400f659be82dc2329e9c36e3b032f38bd42296a8495ac840b0625c097d9Input (hex) (block = 16) :Key (hex) :ddc24663d104e1c2f81f11aef98156503dafdc435f81e3ac3d705015ebab095cDecrypted (plain text) :Hello World AES-GCM, Welcome to Cryptography!

AES Password-Based encryption and decryption.

Para encriptação baseada em palavra-passe, podemos utilizar a Especificação de Criptografia Baseada em Palavra-Passe (PKCS), definida RFC 8018, para gerar uma chave a partir de uma dada palavra-passe.

Para entradas PKCS:

  • Password, fornece-se isto.
  • Sal – Pelo menos 64 bits (8 bytes) bytes aleatórios.
  • Contagem de iteração – Recomendar uma contagem mínima de iteração de 1.000.

O que é sal e contagem de iteração?

  • O salt produz um amplo conjunto de chaves para uma dada palavra-passe. Por exemplo, se o sal for 128 bits, haverá até 2^128 chaves para cada palavra-passe. Por conseguinte, aumenta a dificuldade dos ataques do arco-íris. Além disso, a tabela do arco-íris que os atacantes constroem para a palavra-passe de um utilizador tornou-se inútil para outro utilizador.
  • O iteration count aumenta o custo de produção de chaves a partir de uma palavra-passe, aumentando assim a dificuldade e diminuindo a velocidade dos ataques.

3.1 Para a saída encriptada, prefixamos o 12 bytes IV e password salt ao texto cifrado, porque precisamos do mesmo IV e sal de senha (para chave secreta) para decifração. Além disso, utilizamos Base64 codificador para codificar o texto codificado numa representação em cadeia, para que possamos enviar o texto codificado ou o texto cifrado em formato de cadeia (foi matriz de bytes).

É isto ok se o sal de senha for conhecido publicamente?
É o mesmo com o IV, e é ok que o sal de senha seja conhecido publicamente, o único segredo é a chave, mantendo-a privada e confidencial.

EncryptorAesGcmPassword.java
package com.mkyong.crypto.encryptor;import com.mkyong.crypto.utils.CryptoUtils;import javax.crypto.Cipher;import javax.crypto.SecretKey;import javax.crypto.spec.GCMParameterSpec;import java.nio.ByteBuffer;import java.nio.charset.Charset;import java.nio.charset.StandardCharsets;import java.util.Base64;/** * AES-GCM inputs - 12 bytes IV, need the same IV and secret keys for encryption and decryption. * <p> * The output consist of iv, password's salt, encrypted content and auth tag in the following format: * output = byte {i i i s s s c c c c c c ...} * <p> * i = IV bytes * s = Salt bytes * c = content bytes (encrypted content) */public class EncryptorAesGcmPassword { private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding"; private static final int TAG_LENGTH_BIT = 128; // must be one of {128, 120, 112, 104, 96} private static final int IV_LENGTH_BYTE = 12; private static final int SALT_LENGTH_BYTE = 16; private static final Charset UTF_8 = StandardCharsets.UTF_8; // return a base64 encoded AES encrypted text public static String encrypt(byte pText, String password) throws Exception { // 16 bytes salt byte salt = CryptoUtils.getRandomNonce(SALT_LENGTH_BYTE); // GCM recommended 12 bytes iv? byte iv = CryptoUtils.getRandomNonce(IV_LENGTH_BYTE); // secret key from password SecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt); Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); // ASE-GCM needs GCMParameterSpec cipher.init(Cipher.ENCRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte cipherText = cipher.doFinal(pText); // prefix IV and Salt to cipher text byte cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length) .put(iv) .put(salt) .put(cipherText) .array(); // string representation, base64, send this string to other for decryption. return Base64.getEncoder().encodeToString(cipherTextWithIvSalt); } // we need the same password, salt and iv to decrypt it private static String decrypt(String cText, String password) throws Exception { byte decode = Base64.getDecoder().decode(cText.getBytes(UTF_8)); // get back the iv and salt from the cipher text ByteBuffer bb = ByteBuffer.wrap(decode); byte iv = new byte; bb.get(iv); byte salt = new byte; bb.get(salt); byte cipherText = new byte; bb.get(cipherText); // get back the aes key from the same password and salt SecretKey aesKeyFromPassword = CryptoUtils.getAESKeyFromPassword(password.toCharArray(), salt); Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO); cipher.init(Cipher.DECRYPT_MODE, aesKeyFromPassword, new GCMParameterSpec(TAG_LENGTH_BIT, iv)); byte plainText = cipher.doFinal(cipherText); return new String(plainText, UTF_8); } public static void main(String args) throws Exception { String OUTPUT_FORMAT = "%-30s:%s"; String PASSWORD = "this is a password"; String pText = "AES-GSM Password-Bases encryption!"; String encryptedTextBase64 = EncryptorAesGcmPassword.encrypt(pText.getBytes(UTF_8), PASSWORD); System.out.println("\n------ AES GCM Password-based Encryption ------"); System.out.println(String.format(OUTPUT_FORMAT, "Input (plain text)", pText)); System.out.println(String.format(OUTPUT_FORMAT, "Encrypted (base64) ", encryptedTextBase64)); System.out.println("\n------ AES GCM Password-based Decryption ------"); System.out.println(String.format(OUTPUT_FORMAT, "Input (base64)", encryptedTextBase64)); String decryptedText = EncryptorAesGcmPassword.decrypt(encryptedTextBase64, PASSWORD); System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText)); }}

Output

Terminal
------ AES GCM Password-based Encryption ------Input (plain text) :AES-GSM Password-Bases encryption!Encrypted (base64) :KmrvjnMusJTQo/hB7T5BvlQpvi3bVbdjpZP51NT7I/enrIfSQuDfSK6iXgdPzvUP2IE54mwrKiyHqMkG8224lRZ9tXHcclmdh98I8b3B------ AES GCM Password-based Decryption ------Input (base64) :KmrvjnMusJTQo/hB7T5BvlQpvi3bVbdjpZP51NT7I/enrIfSQuDfSK6iXgdPzvUP2IE54mwrKiyHqMkG8224lRZ9tXHcclmdh98I8b3BDecrypted (plain text) :AES-GSM Password-Bases encryption!

3.2 Se a palavra-chave não corresponder, Java lança AEADBadTagException: Tag mismatch!

 // change the password to something else String decryptedText = EncryptorAesGcmPassword.decrypt(encryptedTextBase64, "other password"); System.out.println(String.format(OUTPUT_FORMAT, "Decrypted (plain text)", decryptedText));

Eliminar

Terminal
Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch! at java.base/com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:623) at java.base/com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1118) at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1055) at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:855) at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446) at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2207) at com.mkyong.crypto.encryptor.EncryptorAesGcmPassword.decrypt(EncryptorAesGcmPassword.java:88) at com.mkyong.crypto.encryptor.EncryptorAesGcmPassword.main(EncryptorAesGcmPassword.java:109)

AES Encriptação e desencriptação de ficheiros.

Este exemplo é uma encriptação de ficheiro AES baseada em palavra-passe. As ideias são as mesmas, mas precisamos de algumas classes IO para trabalhar com os recursos ou ficheiros.

Existe um ficheiro de texto, em resources pasta.

readme.txt
This is line 1.This is line 2.This is line 3.This is line 4.This is line 5.This is line 9.This is line 10.

4.1 Este exemplo é semelhante a 3.1 EncryptorAesGcmPassword.java, com algumas pequenas alterações como o retorno de uma corda codificada byte em vez de uma corda codificada base64.

 public static byte encrypt(byte pText, String password) throws Exception { //... // prefix IV and Salt to cipher text byte cipherTextWithIvSalt = ByteBuffer.allocate(iv.length + salt.length + cipherText.length) .put(iv) .put(salt) .put(cipherText) .array(); // it works, even if we save the based64 encoded string into a file. // return Base64.getEncoder().encodeToString(cipherTextWithIvSalt); // we save the byte into a file. return cipherTextWithIvSalt; }

Adicionar encryptFile e decryptFile para trabalhar com o ficheiro.

 public static void encryptFile(String fromFile, String toFile, String password) throws Exception { // read a normal txt file byte fileContent = Files.readAllBytes(Paths.get(ClassLoader.getSystemResource(fromFile).toURI())); // encrypt with a password byte encryptedText = EncryptorAesGcmPasswordFile.encrypt(fileContent, password); // save a file Path path = Paths.get(toFile); Files.write(path, encryptedText); } public static byte decryptFile(String fromEncryptedFile, String password) throws Exception { // read a file byte fileContent = Files.readAllBytes(Paths.get(fromEncryptedFile)); return EncryptorAesGcmPasswordFile.decrypt(fileContent, password); }

4.2 Leia o ficheiro acima readme.txt do classpath, encripte-o, e os dados encriptados para um novo ficheiro c:\test\readme.encrypted.txt.

 String password = "password123"; String fromFile = "readme.txt"; // from resources folder String toFile = "c:\\test\\readme.encrypted.txt"; // encrypt file EncryptorAesGcmPasswordFile.encryptFile(fromFile, toFile, password);

Output

AES encriptação de ficheiro

4.3 Leia o ficheiro encriptado, descodifique-o, e imprima a saída.

 String password = "password123"; String toFile = "c:\\test\\readme.encrypted.txt"; // decrypt file byte decryptedText = EncryptorAesGcmPasswordFile.decryptFile(toFile, password); String pText = new String(decryptedText, UTF_8); System.out.println(pText);

Output

Terminal
This is line 1.This is line 2.This is line 3.This is line 4.This is line 5.This is line 9.This is line 10.

P.S A encriptação de imagem AES é o mesmo conceito.

Download Source Code

$ git clone https://github.com/mkyong/core-java

$ cd java-crypto

Deixe-me saber se o artigo precisa de ser melhorado. Thanks.

  • Wikipedia – Cipher JavaDoc
  • Wikipedia – Cipher Block Chaining (CBC)
  • Wikipedia – Galois/Counter Mode (GCM)
  • Oracle – KeyGenerator Algorithms JavaDoc
  • Java – Como gerar um 12 bytes aleatórios?
  • Por que não devo utilizar a encriptação ECB?
  • li>Spring Security Crypto Moduleli>Wikipedia – PBKDF2li>RFC 8018 – PKCSli>Java – Como unir e dividir a matriz de bytesli>Java Security Standard Nomes de Algoritmosli>NIST – Recomendação para o Modo Galois/Counter (GCM)

author imageauthor image

mkyong

Founder of Mkyong.com, adoro Java e coisas de código aberto. Siga-o no Twitter. Se gostar dos meus tutoriais, considere fazer uma doação a estas instituições de caridade.

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *