Nesse artigo vou mostrar como criptografar e
descriptografar um texto usando criptografia bidirecional assimétrica, ou seja,
de chave única e privada. Esse tipo de criptografia é útil por exemplo para
enviar uma URL de recuperação de senha para um usuário ou mesmo proteger o
token de acesso.
Vamos usar o algoritmo AES (Advanced Encryption
Standard) que possui algumas variações, da qual vamos usar o CBC - Cipher Block
Chaining (Encadeamento de blocos cifrados). O modo CBC usa um vetor de
inicialização (IV) para aumentar a criptografia. Primeiro, o CBC usa o bloco de
texto simples XOR com o IV. Em seguida, ele criptografa o resultado para o
bloco de texto cifrado. No próximo bloco, ele usa o resultado da criptografia
para XOR com o bloco de texto simples até o último bloco. Uma das principais
características do CBC é que ele usa um processo de encadeamento que faz com
que a descriptografia de um bloco de texto cifrado dependa de todos os blocos
de texto cifrado anteriores.
*XOR = Ou exclusivo ou disjunção exclusiva é um mecanismo de
codificação usado para combinar diferentes entradas.
Vamos aos códigos. Serão 2 classes e um Teste Unitário
para garantirmos que tudo está certo.
import java.security.spec.KeySpec;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Advanced Encryption
Standard ( AES )
*
* @author Daniel Oliveira
*
* IV =
Initialization Vector ou Vetor de inicialização
* é uma
entrada para uma primitiva criptográfica sendo
usada para fornecer
* o estado
inicial. O IV geralmente precisa ser aleatório
ou pseudo -aleatório,
* mas
às vezes um IV só precisa ser imprevisível
ou único.
*
* o AES
tem um tamanho de bloco de 128 bits ou
16 bytes. O AES não altera o
* tamanho,
e o tamanho do texto cifrado é igual ao tamanho
do texto não
* criptografado.
Além disso, nos modos ECB e CBC, devemos usar
um algoritmo
* de
preenchimento como PKCS 5. Portanto, o tamanho dos
dados após a criptografia é: 256
*/
public class SymmetricCripto {
private static final String AES = "AES";
private static final int ITERATION_COUNT = 65536;
private static final int KEY_LENGTH = 256;
private static final String ALGORIHM = "AES/CBC/PKCS5Padding";
private static final String SECRET_KEY_FACTORY_INSTANCE = "PBKDF2WithHmacSHA256";
private static final String ENCODING = "UTF-8";
// crie seu IV com 16 Bytes.
private static final String IV = "8P7A6S5S4W3O2R1D";
public static byte[] cripto(String
text, SecretKey password ) {
try {
return cripto(password, text.getBytes(ENCODING));
} catch (Exception exception) {
exception.printStackTrace();
return new byte[0];
}
}
private static byte[] cripto(SecretKey secretKey, byte[] text ) {
try {
Cipher cipher = Cipher.getInstance(ALGORIHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, generateIv());
return cipher.doFinal(text);
} catch (Exception exception) {
exception.printStackTrace();
return new byte[0];
}
}
public static byte[]
descripto(SecretKey secretKey, byte[] text ) {
try{
Cipher cipher = Cipher.getInstance(ALGORIHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, generateIv());
return cipher.doFinal(text);
} catch (Exception exception) {
exception.printStackTrace();
return new byte[0];
}
}
public static SecretKey
getSecretKey(String password, String salt){
SecretKey secret = null;
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_INSTANCE);
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), ITERATION_COUNT, KEY_LENGTH);
secret = new SecretKeySpec(factory.generateSecret(keySpec)
.getEncoded(), AES);
} catch (Exception e) {
e.printStackTrace();
}
return secret;
}
public static IvParameterSpec
generateIv() {
byte[] iv = IV.getBytes();
return new
IvParameterSpec(iv);
}
}
public final class Util {
private static final String SALT = "12345678";
// crie seu PASSWORD com 16 Bytes.
private
static final
String PASSWORD = "P1A2S3S4W5O6R7D8";
public static String
createToken(String username) {
Timestamp currentTimeStamp = new
Timestamp(Calendar.getInstance().getTime().getTime());
return username + "_" + currentTimeStamp.toString();
}
public static String
encodeToken(String token) {
SecretKey secretKey =
SymmetricCripto.getSecretKey(PASSWORD, SALT);
byte[] criptografado = SymmetricCripto.cripto(token, secretKey);
return Base64.getUrlEncoder().encodeToString(criptografado);
}
public static String
decodeToken(String token) {
byte[] decodedBytes = Base64.decode(token.getBytes());
SecretKey secretKey = SymmetricCripto.getSecretKey(PASSWORD, SALT);
token = new String(SymmetricCripto.descripto(secretKey, decodedBytes), Charset.forName("UTF-8"));
return token;
}
}
E agora
para testar a criptografia vamos ver a classe de teste:
public class UtilTeste {
@Test
public void
criptografaTokenTest() {
String token = Util.createToken("Daniel");
String tokenCriptografado = Util.encodeToken(token);
String tokenDescriptografado = Util.decodeToken(tokenCriptografado);
Assert.assertTrue(token.equals(tokenDescriptografado));
}
@Test
public void criptografaTokenTest2() {
String token = Util.createToken("Daniel");
String token2 = Util.createToken("Gadiel");
System.out.println(token);
System.out.println(token2);
String tokenCriptografado1 = Util.encodeToken(token);
System.out.println(tokenCriptografado1);
String tokenCriptografado2 = Util.encodeToken(token2);
System.out.println(tokenCriptografado2);
String tokenDescriptografado1 = Util.decodeToken(tokenCriptografado1);
System.out.println(tokenDescriptografado1);
String tokenDescriptografado2 = Util.decodeToken(tokenCriptografado2);
System.out.println(tokenDescriptografado2);
Assert.assertTrue(token.equals(tokenDescriptografado1) && token2.equals(tokenDescriptografado2));
}
@Test
public void criptografaTokenTest3() {
String token = "Daniel_2023-01-10
18:58:09.642";
String token2 = "Gadiel_2023-01-10
18:58:09.642";
System.out.println(token);
System.out.println(token2);
String tokenCriptografado1 = Util.encodeToken(token);
System.out.println(tokenCriptografado1);
String tokenCriptografado2 = Util.encodeToken(token2);
System.out.println(tokenCriptografado2);
String tokenDescriptografado1 = Util.decodeToken(tokenCriptografado1);
System.out.println(tokenDescriptografado1);
String tokenDescriptografado2 = Util.decodeToken(tokenCriptografado2);
System.out.println(tokenDescriptografado2);
tokenCriptografado1.equals("KOlEIFpg8oWmgIPQlwIZ9Ibaz4DU0Fq3Fx7ZWHwmLKw=") && tokenCriptografado2.equals("7NzCr0fzeSNy9X2Ib+mjuFPY3EmTM03gpThBW5q6A3o="));
}
}
Importante: Para se ter sempre o mesmo resultado de
criptografia e descriptografia para valores iguais após a reinicialização do
servidor ou em servidores diferentes (para quem usa cluster ou coisa do tipo) o
PassWord que gera a SecretKey e o IV devem ser criados com o mesmo valor
sempre. Note que eles são constantes no código.
Dica: O token pode (e deve) ser melhorado com o acréscimo
de um número randômico e mais dados de acordo com a necessidade. O PassWord e o IV também devem ser alterados de tempos em tempos.
Solução adaptada à necessidade que tive a partir das
seguintes fontes:
https://www.devmedia.com.br/introducao-a-seguranca-da-informacao-em-java/28247
https://www.baeldung.com/java-aes-encryption-decryption
https://www.techtarget.com/searchsecurity/definition/cipher-block-chaining
Nenhum comentário:
Postar um comentário