O termo segurança se refere à proteção de um bem contra riscos e perdas, e nos dias atuais, a informação é o tesouro mais precioso para uma empresa. Garantir que pessoas erradas não ponham a mão nessas informações se torna cada dia mais crucial, já que os dados são recursos estratégicos para muitas empresas. Em função disso a segurança da informação vem sendo cada vez mais difundida. O objetivo desse post é fazer uma pequena introdução desse assunto utilizando a tecnologia Java.

Segurança da Informação está relacionada ao fato de proteger um conjunto de informações de uma pessoa ou empresa independente do seu valor, por exemplo, tal informação pode valer milhões de dólares ou apenas um grande valor sentimental. A métrica para o valor da informação em alguns casos é de valia pessoal, como citado anteriormente, a informação pode ser um produto que será lançado no mercado e deve ser protegido para que as empresas concorrentes não saibam ou simplesmente a foto que uma mãe tirou de sua filha quando ela era recém-nascida. Para proteger esses dados, a SI possui cinco atributos que são:

  • Confidencialidade - propriedade que limita o acesso à informação tão somente às entidades legítimas, ou seja, àquelas autorizadas pelo proprietário da informação.
  • Integridade - propriedade que garante que a informação manipulada mantenha todas as características originais estabelecidas pelo proprietário da informação, incluindo controle de mudanças e garantia do seu ciclo de vida (nascimento, manutenção e destruição).
  • Disponibilidade - propriedade que garante que a informação esteja sempre disponível para o uso legítimo, ou seja, por aqueles usuários autorizados pelo proprietário da informação.
  • Autenticidade - propriedade que garante que a informação é proveniente da fonte anunciada e que não foi alvo de mutações ao longo de um processo.
  • Irretratabilidade ou não repúdio - propriedade que garante a impossibilidade de negar a autoria em relação a uma transação anteriormente feita.

E para garantir esses cinco atributos existem dois meios: meio físico (através de fechadura, portas, muros, etc.) e o meio lógico (criptografia, certificado digital, etc.). Nesse post apenas será focado o meio lógico, assim, serão descritas apenas essas estratégias.

A criptografia, termo que significa o estudo dos segredos, tem com o seu objetivo tornar a informação irreconhecível, com exceção para o emissor e o seu respectivo destinatário. A criptografia é dividida em três grandes classes ou blocos:

  • O algoritmo de Hash que visa a criptografia unidirecional, ou seja, uma vez criptografada não tem como descriptografar (descriptografar é o processo de tornar a mensagem visível novamente, é o processo reverso de criptografar). Os tipos mais conhecidos são o MD5 e o SHA-1.
  • Os algoritmos de chave simétrica ou de chave única são criptografias bidirecionais, ou seja, é possível criptografar e descriptografar a mensagem original a partir de apenas uma senha. Isso quer dizer que a mesma senha na qual eu criptografo uma informação eu a retorno em seu modo legível ou original.
  • Os algoritmos de chave assimétrica ou de par de chaves são criptografias bidirecionais e são caracterizados por um par de chaves, uma pública e uma privada. A chave pública é distribuída livremente e a privada é restrita apenas para o dono da chave. Assim, uma mensagem que é criptografada por uma chave pública só poderá ser descriptografada pela sua chave privada respectiva. A criptografia assimétrica mais popular é o algoritmo RSA.

Assinatura digital tem como meta garantir que o emissor é realmente quem ele afirma.

A certificação digital é uma tecnologia que provê um mecanismo seguro, capaz de fornecer autenticidade, confidencialidade e integridade a toda e qualquer informação eletrônica, sempre por meio de um certificado digital.

Uma coisa que deve ser citada é que a segurança é, em alguns casos, inimiga da performance. Em outras palavras, o processo de usar criptografia, assinatura digital, dentre outros recursos de segurança deixará a aplicação mais lenta, assim é interessante proteger informações que realmente precisam ser protegidas, do contrário, sua aplicação perderá desempenho sem necessidade.

Abordada a teoria, o próximo passo será realizar todos esses testes na prática, para isso será criado um pequeno projeto que visa mostrar os recursos de segurança que podem ser utilizados no Java. O primeiro passo será a criação da criptografia unidirecional via hash. Em termo de performance ele tende a ser o mais rápido dos métodos, no entanto, vale salientar que é “uma viagem sem volta”, um dos campos mais indicados para o seu uso, por exemplo, é uma senha no banco de dados.


package br.org.javabahia.criptoutil;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Logger;

public enum Hash {

	SHA {@Override String algorithm() {return SHA_256_ALGORITHM;}	},
	MD5 {@Override String algorithm() {return MD5_ALGORITHM;}};
	
	private static final String MD5_ALGORITHM = "MD5";
	private static final String SHA_256_ALGORITHM = "SHA-256";

	public String hashing(String word) {
		 	MessageDigest messageDigest = getMessageDigest();
	        messageDigest.update(word.getBytes());
	        byte bytes[] = messageDigest.digest();
	        StringBuilder textHexa = new StringBuilder();
	        for(byte data:bytes){
	        	textHexa.append(Integer.toString((data & 0xff) + 0x100, 16).substring(1));	
	        }
	        
	        return textHexa.toString().toUpperCase();
	}
	
	private MessageDigest getMessageDigest(){
		try {
			return MessageDigest.getInstance(algorithm());
		} catch (NoSuchAlgorithmException exception) {
			Logger.getLogger(Hash.class.getName()).severe(exception.getMessage());
			return null;
		}
	} 
	abstract String algorithm();
}
Listagem 1. Código da criptografia hash no MD5 e no SHA

import junit.framework.Assert;
import org.junit.Test;

public class HashTest {

	private static final String word = "soujavaBahia";

	@Test
	public void hashMD5Test() {
		String hash=Hash.MD5.hashing(word);
		System.out.println(hash);
		Assert.assertNotNull(hash);
	}
	
	@Test
	public void hashSHATest() {
		String hash=Hash.SHA.hashing(word);
		System.out.println(hash);
		Assert.assertNotNull(hash);
	}
}
Listagem 2. Código em execução

As saídas resultantes do código acima são 5BD82D0FFEC29239C89BE733A336F01C para MD5 e 493B5502BAA634C14EC0E24D8BE10141104CEDB3593C9C65029CE93366FB2C3E para SHA

O próximo passo será usar a senha simétrica, ela é bidirecional, ou seja, você consegue criptografar e descriptografar um documento. Esse método é interessante para se ocultar informações sem envia-la, por exemplo, aquelas que precisam ser protegidas apenas para serem guardadas em bases de dados. Assim, se criptografa no banco de dados e quando for usa-la, descriptografa. Transportar informações nesse modelo não é interessante, uma vez que também seria necessário que a senha, de alguma forma, seja exportada.


import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public enum Symmetric {
INSTANCE;

private static final String AES = "AES";
private static final int ITERATION_COUNT = 65536;
private static final int KEY_LENGTH = 256;
private static final String ENCODING = "UTF-8";
private static final String CIPHER_INSTANCE ="AES/CBC/PKCS5Padding";
private static final String SECRET_KEY_FACTORY_INSTANCE = "PBKDF2WithHmacSHA1";
private static final SecretKeyFactory FACTORY;
private static final Cipher CIPHER;
private static final byte[] SALT;
static{
		try{
		FACTORY = SecretKeyFactory.getInstance(SECRET_KEY_FACTORY_INSTANCE);
		CIPHER = Cipher.getInstance(CIPHER_INSTANCE);
		}catch(Exception exception){
			Logger.getLogger(Symmetric.class.getName()).severe(exception.getMessage());
			throw new RuntimeException("Error",exception);
		}
	    SALT = RandomSalt.INSTANCE.getRandomSalt();
	    
	
}

public byte[] descripto(String password,byte[] text)   {
	SecretKey secretKey=getSecretKey(password);
	AlgorithmParameters params = CIPHER.getParameters();
	
	try{
	    byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
		CIPHER.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
    	return CIPHER.doFinal(text);
	} catch (IllegalBlockSizeException | BadPaddingException| InvalidKeyException
 |InvalidParameterSpecException |InvalidAlgorithmParameterException exception) {
	Logger.getLogger(Symmetric.class.getName()).severe(exception.getMessage());
	return new byte[0];
}

}
public byte[] cripto(String password,String text)   {
	try {
		return cripto(password, text.getBytes(ENCODING));
	} catch (UnsupportedEncodingException exception) {
		Logger.getLogger(Symmetric.class.getName()).severe(exception.getMessage());
		return new byte[0];
	}
}
public byte[] cripto(String password,byte[] text)   {

	SecretKey secretKey=getSecretKey(password);
	
	try {
		CIPHER.init(Cipher.ENCRYPT_MODE, secretKey);
		return CIPHER.doFinal(text);
	} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException exception) {
		Logger.getLogger(Symmetric.class.getName()).severe(exception.getMessage());
		return new byte[0];
	}
	
	
	
}


private SecretKey getSecretKey(String password){
	
	char[] passwordChar=password.toCharArray();
	KeySpec keySpec = new PBEKeySpec(passwordChar, SALT, ITERATION_COUNT, KEY_LENGTH);
	try {
		SecretKey tmp= FACTORY.generateSecret(keySpec);
		SecretKey secret = new SecretKeySpec(tmp.getEncoded(), AES);
		return secret;
	} catch (InvalidKeySpecException exception) {
		Logger.getLogger(Symmetric.class.getName()).severe(exception.getMessage());
		return null;	
	}
	
	
}

}
Listagem 3. Criptografia simétrica

A Listagem 3 demonstra a criptografia simétrica. Quando a classe é iniciada é criada a fábrica de chaves (atributo FACTORY) responsável por criar as chaves, é carregado o SALTA (um array de bytes aleatório que é adicionado à senha criando assim uma senha mais segura) e o objeto responsável por criptografar e descriptografar o conteúdo a partir da senha (atributo CYPHER).


import junit.framework.Assert;

import org.junit.Test;

public class SimetricTest {

	
	private static final String WORD = "O Java user groups are JavaBahia and SouJava";
	private static final String PASSWORD = "javaJabahia";

	@Test
	public void cripto(){
		byte[] cripto=Symmetric.INSTANCE.cripto(PASSWORD, WORD);
		Assert.assertNotNull(cripto);
	}
	@Test
	public void desCripto(){
		byte[] cripto=Symmetric.INSTANCE.cripto(PASSWORD, WORD);
		String result=new String(Symmetric.INSTANCE.descripto(PASSWORD, cripto));
		System.out.println(new String(cripto));
		System.out.println(result);
		Assert.assertEquals(result, WORD);
		
	}
}
Listagem 4. Criptografando e descriptografando com chave simétrica uma palavra

As saídas do código acima são a frase criptografada, que é composta por um grupo de caracteres não legíveis diretamente, e a própria frase descriptografada.

Para finalizar os modelos de criptografia bidirecional, o próximo modelo será assimétrico, ele é interessante para trocar informações secretas, já que com o par de chaves é necessário apenas enviar a chave pública para a troca de informações. Ele requer maior poder computacional se comparado aos dois últimos, assim, é necessário tomar cautela para criptografar conteúdo muito grande.


import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.logging.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

public enum Asymmetric {
INSTANCE;

private static final int KEY_SIZE = 2048;
private static final String RSA = "RSA";
private static final KeyPairGenerator KEY_PAIR_GENERATOR;
private static final Cipher CIPHER;
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final KeyFactory RSA_KEY_FACTORY;
	static {

		try {
			KEY_PAIR_GENERATOR = KeyPairGenerator.getInstance(RSA);
			KEY_PAIR_GENERATOR.initialize(KEY_SIZE, SECURE_RANDOM);
			RSA_KEY_FACTORY = KeyFactory.getInstance(RSA);			
			CIPHER = Cipher.getInstance(RSA);
		} catch (NoSuchAlgorithmException | NoSuchPaddingException exception) {
			Logger.getLogger(Asymmetric.class.getName()).severe(exception.getMessage());
			throw new RuntimeException("Ocorreu um erro ao iniciar a classe Assimétrica: ",
exception);

		}
	}

	public PublicKey toPublicKey(byte[] encoded){
		
		X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(encoded);
		try {
			return RSA_KEY_FACTORY.generatePublic(publicKeySpec);
		} catch (InvalidKeySpecException exception) {
			    Logger.getLogger(Asymmetric.class.getName()).severe(
"Ocorreu um erro durante a recuperação da chave: ".concat(exception.getMessage())); 
			    return null;
		} 
	}
	
    
   public PrivateKey toPrivateKey(byte[] encoded){
		
	    PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encoded);
		try {
			return RSA_KEY_FACTORY.generatePrivate(privateKeySpec);
		} catch (InvalidKeySpecException exception) {
			    Logger.getLogger(Asymmetric.class.getName()).severe(
"Ocorreu um erro durante a recuperação da chave: ".concat(exception.getMessage())); 
			    return null;
		} 
	}
	public byte[] desCripto(PrivateKey privateKey, byte[] cipherText) {
		try {
			CIPHER.init(Cipher.DECRYPT_MODE, privateKey);
			return CIPHER.doFinal(cipherText);
		} catch (IllegalBlockSizeException | BadPaddingException| InvalidKeyException exception) {
			Logger.getLogger(Asymmetric.class.getName()).severe(exception.getMessage());
			return new byte[0];
		}
	}
		
	public byte[] cripto(PublicKey publicKey,String word) {
		try {
			return cripto(publicKey, word.getBytes("UTF8"));
		} catch (UnsupportedEncodingException exception) {
			Logger.getLogger(Asymmetric.class.getName()).severe(exception.getMessage());
			return new byte[0];
		}
	}
	
	public byte[] cripto(PublicKey publicKey,byte[] word) {
		try{
		CIPHER.init(Cipher.ENCRYPT_MODE, publicKey); 
		return CIPHER.doFinal(word); 
		}catch(IllegalBlockSizeException | BadPaddingException | InvalidKeyException exception){
			Logger.getLogger(Asymmetric.class.getName()).severe(exception.getMessage());
			return new byte[0];
		}
	}
	
	public KeyPair createKeyPair() {
		return KEY_PAIR_GENERATOR.generateKeyPair();
	}
}
Listgem 5. Exemplo de criptografia assimétrica

Nesse código a fábrica de chaves é construída no período de inicialização da classe. Uma vez iniciada, se pode criar o par de chaves. Enquanto a chave pública é responsável por gerar a criptografia, a chave privada é responsável por efetuar o processo contrário. Na Listagem 6 é exibida a execução dessa criptografia, em que uma palavra é criptografada com a chave pública e descriptografada com a chave privada. Também é possível gerar um stream, um array de bytes, das chaves públicas e privadas, com isso se pode, por exemplo, enviar a chave via e-mail, colocar em um arquivo. Etc.


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.logging.Logger;

import junit.framework.Assert;

import org.junit.Test;

public class AsymmetricTest {

	
	private static final String WORD = " Java is everywhere and anywhere ";
	private static final String PRIAVTE_FILE = "public.rsa";
	private static final String PUBLIC_FILE = "private.public";
	@Test
	public void createKeyPairTest(){
		Assert.assertNotNull(Asymmetric.INSTANCE.createKeyPair());
	}
	
	@Test
	public void createPublicPrivateTest() throws Exception{

		File publicFile=new File(PUBLIC_FILE);
		File privateFile=new File(PRIAVTE_FILE);
		
		toFile(publicFile, privateFile);
		
		byte[] publicEncoed=readFile(publicFile);
		byte[] privateEncoded=readFile(privateFile);
		
		
		PublicKey rsaPubKey = Asymmetric.INSTANCE.toPublicKey(publicEncoed);
		PrivateKey rsaPriKey = Asymmetric.INSTANCE.toPrivateKey(privateEncoded);
		
		byte[] cripto=Asymmetric.INSTANCE.cripto(rsaPubKey, WORD);
		Asymmetric.INSTANCE.desCripto(rsaPriKey, cripto);
	}

	private void toFile(File publicFile, File privateFile) {
		KeyPair keyPair=Asymmetric.INSTANCE.createKeyPair();		
		writeFile(privateFile, keyPair.getPrivate().getEncoded());
		writeFile(publicFile, keyPair.getPublic().getEncoded());
	}
	private byte[] readFile(File file){
		byte[] datas=new byte[(int)file.length()];
		try{
		FileInputStream stream=new FileInputStream(file);
		stream.read(datas);
		return datas;
		}catch(IOException exception){
			return new byte[0];
		}
		
	}
	private void writeFile(File file,byte[] data){
		try(FileOutputStream stream=new FileOutputStream(file)) {
			stream.write(data);
		} catch (IOException ioException) {
			Logger.getLogger(AsymmetricTest.class.getName()).severe(ioException.getMessage());
		}
		
	}
		
	@Test
	public void criptoTest(){
		KeyPair keyPair=Asymmetric.INSTANCE.createKeyPair();
		PublicKey publicKey=keyPair.getPublic();
		byte[] cipherText= Asymmetric.INSTANCE.cripto(publicKey, WORD);
		Assert.assertNotNull(cipherText);
	}
	
	@Test
	public void desCriptoTest(){
		KeyPair keyPair=Asymmetric.INSTANCE.createKeyPair();
		PublicKey publicKey=keyPair.getPublic();
		PrivateKey privateKey=keyPair.getPrivate();
		byte[] cipherText= Asymmetric.INSTANCE.cripto(publicKey, WORD);
		byte[] desCipherText=Asymmetric.INSTANCE.desCripto(privateKey, cipherText);
		Assert.assertEquals(new String(desCipherText), WORD);
	}
	
}
Listagem 6. Executando a criptografia assimétrica

Nesse artigo foram discutidos os princípios de segurança e como utilizá-los no Java. Estes métodos podem ser bastante úteis no desenvolvimento de aplicações em um cenário onde a segurança dos dados tem se mostrado cada dia mais importante.


Referências

  • Wikipedia - Segurança da Informação
  • Matté, Marcio Angelo. Certificação Digital com Java, Revista Java Magazine 110

Leia Também