Criptografando e descriptografando arquivos em Java
1. Visão geral
Neste tutorial, daremos uma olhada em como criptografar e descriptografar um arquivo usando APIs JDK existentes.
2. Escrevendo um teste primeiro
Vamos começar escrevendo nosso teste, estilo TDD. Já que vamos trabalhar com arquivos aqui, um teste de integração parece ser apropriado.
Como estamos apenas usando a funcionalidade JDK existente, nenhuma dependência externa é necessária.
Primeiro,we’ll encrypt the content using a newly generated secret key (estamos usando AES,Advanced Encryption Standard, como o algoritmo de criptografia simétrica neste exemplo).
Observe também que estamos definindo a string de transformação completa no construtor (AES/CBC/PKCS5Padding), que é uma concatenação da criptografia usada, modo de criptografia de bloco e preenchimento (algorithm/mode/padding). As implementações JDK suportam uma série de transformações diferentes por padrão, mas observe que nem todas as combinações ainda podem ser consideradas criptograficamente seguras pelos padrões atuais.
Assumiremosour FileEncrypterDecrypter class will write the output to a file called baz.enc. Depois,we decrypt this file using the same secret keye verifique se o conteúdo descriptografado é igual ao conteúdo original:
@Test
public void whenEncryptingIntoFile_andDecryptingFileAgain_thenOriginalStringIsReturned() {
String originalContent = "foobar";
SecretKey secretKey = KeyGenerator.getInstance("AES").generateKey();
FileEncrypterDecrypter fileEncrypterDecrypter
= new FileEncrypterDecrypter(secretKey, "AES/CBC/PKCS5Padding");
fileEncrypterDecrypter.encrypt(originalContent, "baz.enc");
String decryptedContent = fileEncrypterDecrypter.decrypt("baz.enc");
assertThat(decryptedContent, is(originalContent));
new File("baz.enc").delete(); // cleanup
}
3. Criptografia
Vamos inicializar a cifra no construtor de nossa classeFileEncrypterDecrypter usando a transformação especificadaString.
Isso nos permite falhar cedo, caso uma transformação incorreta tenha sido especificada:
FileEncrypterDecrypter(SecretKey secretKey, String transformation) {
this.secretKey = secretKey;
this.cipher = Cipher.getInstance(transformation);
}
Podemos entãouse the instantiated cipher and the provided secret key to perform the encryption:
void encrypt(String content, String fileName) {
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] iv = cipher.getIV();
try (FileOutputStream fileOut = new FileOutputStream(fileName);
CipherOutputStream cipherOut = new CipherOutputStream(fileOut, cipher)) {
fileOut.write(iv);
cipherOut.write(content.getBytes());
}
}
Java nos permiteleverage the convenient CipherOutputStream class for writing the encrypted content into another OutputStream.
Observe que estamos gravando o IV (Initialization Vector) no início do arquivo de saída. Neste exemplo, o IV é gerado automaticamente ao inicializar oCipher.
O uso de um IV é obrigatório ao usar o modo CBC, para randomizar a saída criptografada. O IV, entretanto, não é considerado um segredo, portanto, não há problema em escrevê-lo no início do arquivo.
4. Descriptografia
Para descriptografar, também precisamos ler o IV primeiro. Depois, podemos inicializar nossa cifra e descriptografar o conteúdo.
Novamente, podemos fazer uso de uma classe Java especial,CipherInputStream, which transparently takes care of the actual decryption:
String decrypt(String fileName) {
String content;
try (FileInputStream fileIn = new FileInputStream(fileName)) {
byte[] fileIv = new byte[16];
fileIn.read(fileIv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(fileIv));
try (
CipherInputStream cipherIn = new CipherInputStream(fileIn, cipher);
InputStreamReader inputReader = new InputStreamReader(cipherIn);
BufferedReader reader = new BufferedReader(inputReader)
) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
content = sb.toString();
}
}
return content;
}
5. Conclusão
Vimos que podemos realizar criptografia e descriptografia básicas usando classes JDK padrão, comoCipher,CipherOutputStreameCipherInputStream.
Como de costume, o código completo deste artigo está disponível em nossoGitHub repository.
Além disso, você pode encontrar uma lista das Cifras disponíveis no JDKhere.
Por fim, observe que os exemplos de código aqui não são para código de produção e as especificações de seu sistema precisam ser consideradas completamente ao usá-los.