sexta-feira, maio 30, 2008

Criptografia RSA

Posto abaixo um programa que escrevi faz alguns anos para servir de exemplo de uso das APIs de criptografia do Java. Dei uma reformatada (com o bom e velho Eclipse) e incluí alguns javadocs. Não é o jeito "correto" de usar as APIs, mas muita gente precisa de algo parecido com isso para trabalhos de escola, portanto divirtam-se!

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.spec.RSAKeyGenParameterSpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

// Exemplo de criptografia RSA.
//
// Para criptografar um bloco de dados com RSA, gera-se uma chave aleatória para
// usar com um algoritmo simétrico (como o AES), e então criptografa-se essa chave
// com o algoritmo RSA usando-se a chave pública do destinatário.
//
// Para o destinatário decifrar os dados, ele deve decifrar a chave recebida com sua
// chave privada, e então usar a chave decifrada para decifrar os dados.
//
// Note que o modo correto de fazer isso é usar um formato padronizado, como o PKCS#7
// (ou CMS), e usar o BouncyCastle. Só faço isto para exemplificar as APIs do Java.
//
/**
* Efetua a parte da criptografia
*/
class Cifrador {
/**
* Cifra um bloco de dados com a chave pública.
*
* @param pub
* A chave pública.
* @param textoClaro
* O bloco de dados a ser cifrado.
* @return Dois arrays de bytes, sendo que o primeiro é o bloco cifrado, e o
* segundo é a chave gerada e cifrada. Não jogue fora nenhum deles.
* @throws NoSuchAlgorithmException
* Algoritmo (AES) não disponível na sua versão do JDK.
* @throws NoSuchPaddingException
* Padding (PKCS5Padding) não disponível na sua versão do JDK.
* @throws InvalidKeyException
* Se a chave pública for inválida.
* @throws IllegalBlockSizeException
* Não deve ocorrer.
* @throws BadPaddingException
* Não deve ocorrer.
* @throws InvalidAlgorithmParameterException
* Não deve ocorrer.
*/
public byte[][] cifra(PublicKey pub, byte[] textoClaro)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, InvalidAlgorithmParameterException {
byte[] textoCifrado = null;
byte[] chaveCifrada = null;

// -- A) Gerando uma chave simétrica de 128 bits
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(128);
SecretKey sk = kg.generateKey();
byte[] chave = sk.getEncoded();
// -- B) Cifrando o texto com a chave simétrica gerada
Cipher aescf = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivspec = new IvParameterSpec(new byte[16]);
aescf.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(chave, "AES"),
ivspec);
textoCifrado = aescf.doFinal(textoClaro);
// -- C) Cifrando a chave com a chave pública
Cipher rsacf = Cipher.getInstance("RSA");
rsacf.init(Cipher.ENCRYPT_MODE, pub);
chaveCifrada = rsacf.doFinal(chave);

return new byte[][] { textoCifrado, chaveCifrada };
}
}

/**
* Efetua a parte da decifração
*/
class Decifrador {
/**
* Decifra o bloco de dados e a chave cifrada usando a chave privada do destinatário.
* @param pvk A chave privada.
* @param textoCifrado Os dados cifrados.
* @param chaveCifrada A chave cifrada.
* @return Os dados decifrados.
* @throws NoSuchAlgorithmException Algoritmo AES não disponível na sua versão do JDK.
* @throws NoSuchPaddingException PKCS5Padding não disponível na sua versão do JDK.
* @throws InvalidKeyException Se a chave passada for inválida.
* @throws IllegalBlockSizeException Não deve ocorrer.
* @throws BadPaddingException Se houver um erro de decifração (chave incorreta ou texto
* cifrado incorreto, por exemplo)
* @throws InvalidAlgorithmParameterException Não deve ocorrer.
*/
public byte[] decifra(PrivateKey pvk, byte[] textoCifrado,
byte[] chaveCifrada) throws NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException,
InvalidAlgorithmParameterException {
byte[] textoDecifrado = null;

// -- A) Decifrando a chave simétrica com a chave privada
Cipher rsacf = Cipher.getInstance("RSA");
rsacf.init(Cipher.DECRYPT_MODE, pvk);
byte[] chaveDecifrada = rsacf.doFinal(chaveCifrada);
// -- B) Decifrando o texto com a chave simétrica decifrada
Cipher aescf = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivspec = new IvParameterSpec(new byte[16]);
aescf.init(Cipher.DECRYPT_MODE,
new SecretKeySpec(chaveDecifrada, "AES"), ivspec);
textoDecifrado = aescf.doFinal(textoCifrado);

return textoDecifrado;
}
}
/**
* Serve para carregar a chave pública de um arquivo. Não é o melhor jeito
* de guardar a chave (é melhor e mais seguro usar um keystore),
* mas faço isso só para simplificar.
*/
class CarregadorChavePublica {
/**
* Carrega a chave pública serializada de um arquivo.
* @param fPub O arquivo com a chave pública serializada.
* @return A chave pública
* @throws IOException Se não achar o arquivo, ou se houver algum problema
* ao efetuar a des-serialização.
* @throws ClassNotFoundException O objeto contido no arquivo é de uma classe não presente
* neste projeto.
*/
public PublicKey carregaChavePublica(File fPub) throws IOException,
ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fPub));
PublicKey ret = (PublicKey) ois.readObject();
ois.close();
return ret;
}
}

/**
* Serve para carregar a chave privada de um arquivo. Não é o melhor jeito
* de guardar a chave (é melhor e mais seguro usar um keystore),
* mas faço isso só para simplificar.
*/
class CarregadorChavePrivada {
/**
* Carrega a chave privada serializada de um arquivo.
* @param fPvk O arquivo com a chave privada serializada.
* @return A chave privada.
* @throws IOException Se não achar o arquivo, ou se houver algum problema
* @throws ClassNotFoundException O objeto contido no arquivo é de uma classe não presente
* neste projeto.
*/
public PrivateKey carregaChavePrivada(File fPvk) throws IOException,
ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fPvk));
PrivateKey ret = (PrivateKey) ois.readObject();
ois.close();
return ret;
}
}

/**
* Gera um par de chaves e as guarda em formato serializado.
* (não é o formato mais seguro - seria melhor usar um keystore, que pode ser protegido
* por senha), mas faço isto para simplificar a compreensão.
*/
class GeradorParChaves {
private static final int RSAKEYSIZE = 1024;

/**
* Gera um par de chaves e as guarda em formato serializado em arquivos.
* @param fPub O arquivo que irá conter a chave pública.
* @param fPvk O arquivo que irá conter a chave privada.
* @throws IOException Problemas de acesso/gravação do arquivo.
* @throws NoSuchAlgorithmException RSA não disponível nesta versão do JDK.
* @throws InvalidAlgorithmParameterException Não deve ocorrer.
* @throws CertificateException Não deve ocorrer.
* @throws KeyStoreException Não deve ocorrer.
*/
public void geraParChaves(File fPub, File fPvk) throws IOException,
NoSuchAlgorithmException, InvalidAlgorithmParameterException,
CertificateException, KeyStoreException {

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(new RSAKeyGenParameterSpec(RSAKEYSIZE,
RSAKeyGenParameterSpec.F4));
KeyPair kpr = kpg.generateKeyPair();
PrivateKey priv = kpr.getPrivate();
PublicKey pub = kpr.getPublic();

// -- Gravando a chave pública em formato serializado
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
fPub));
oos.writeObject(pub);
oos.close();

// -- Gravando a chave privada em formato serializado
// -- Não é a melhor forma (deveria ser guardada em um keystore, e
// protegida por senha),
// -- mas isto é só um exemplo
oos = new ObjectOutputStream(new FileOutputStream(fPvk));
oos.writeObject(priv);
oos.close();

}
}

/**
* A classe a seguir cifra a decifra a string "Hello, world!".
*/
class ExemploCriptografia {
/**
* Mostra um array de bytes como um "dump".
* @param b O array de bytes que deve ser mostrado em formato dump.
*/
static void printHex(byte[] b) {
if (b == null) {
System.out.println("(null)");
} else {
for (int i = 0; i < b.length; ++i) {
if (i % 16 == 0) {
System.out.print(Integer
.toHexString((i & 0xFFFF) | 0x10000)
.substring(1, 5)
+ " - ");
}
System.out.print(Integer.toHexString((b[i] & 0xFF) | 0x100)
.substring(1, 3)
+ " ");
if (i % 16 == 15 || i == b.length - 1) {
int j;
for (j = 16 - i % 16; j > 1; --j)
System.out.print(" ");
System.out.print(" - ");
int start = (i / 16) * 16;
int end = (b.length < i + 1) ? b.length : (i + 1);
for (j = start; j < end; ++j)
if (b[j] >= 32 && b[j] <= 126)
System.out.print((char) b[j]);
else
System.out.print(".");
System.out.println();
}
}
System.out.println();
}
}

/**
* O programa principal
* @param args Os argumentos são ignorados.
* @throws Exception Lanço uma Exception para não poluir muito o código de demonstração
* com tratamento de exceções. Em um programa "de verdade" você deveria
* tratar corretamente as exceções.
*/
public static void main(String[] args) throws Exception {
// -- Gera o par de chaves, em dois arquivos (chave.publica e
// chave.privada)
GeradorParChaves gpc = new GeradorParChaves();
gpc.geraParChaves(new File("chave.publica"), new File("chave.privada"));

// -- Cifrando a mensagem "Hello, world!"
byte[] textoClaro = "Hello, world!".getBytes("ISO-8859-1");
CarregadorChavePublica ccp = new CarregadorChavePublica();
PublicKey pub = ccp.carregaChavePublica(new File("chave.publica"));
Cifrador cf = new Cifrador();
byte[][] cifrado = cf.cifra(pub, textoClaro);
printHex(cifrado[0]);
printHex(cifrado[1]);

// -- Decifrando a mensagem
CarregadorChavePrivada ccpv = new CarregadorChavePrivada();
PrivateKey pvk = ccpv.carregaChavePrivada(new File("chave.privada"));
Decifrador dcf = new Decifrador();
byte[] decifrado = dcf.decifra(pvk, cifrado[0], cifrado[1]);
// System.out.println (new String (textoClaro, "ISO-8859-1"));
printHex(decifrado);
}
}

terça-feira, maio 27, 2008

Formatação de Números

Se você deseja converter um double ou um BigDecimal de/para uma string em formato monetário - R$ 12.345.678,90 por exemplo - então use java.util.NumberFormat , método getCurrencyInstance. Isso economiza um monte de tempo tentando tirar e pôr pontos, vírgulas e outras coisas ineficientes (e que podem estar erradas também).
Exemplo:
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

class NumberFormatComMoeda {
public static void main(String[] args) {
NumberFormat nf = NumberFormat.getCurrencyInstance (new Locale ("pt", "BR"));
double d = 12345678.90;
String s = nf.format (d);
System.out.println (s); // deve imprimir "R$ 12.345.678,90"
// Para os que preferem double:
try {
double d1 = nf.parse (s).doubleValue();
System.out.println (d1); // imprime "1.23456789E7"
} catch (ParseException ex) {
ex.printStackTrace();
}
// Para os que preferem BigDecimal:
((DecimalFormat) nf).setParseBigDecimal(true);
try {
BigDecimal bd = (BigDecimal) (nf.parse(s));
System.out.println (bd); // imprime "12345678.90"
} catch (ParseException ex) {
ex.printStackTrace();
}
}
}