Logo deveria haver uma forma de criptografia disponível tanto no servidor como nos clientes, inicialmente um serviço em Java e o cliente seria no Android (depois outros surgiriam), com isso tem quer ser usado um algorítimo de chave simétrica, que permitisse recuperar a mensagem original de posse da chave de criptografia usada. Obviamente deveria ser um mecanismo eficiente.
Escolhi o AES por acreditar ser o padrão ideal atualmente, "lançado" em 2001 entrou em estudo pelo governo dos EUA e hoje é o seu padrão para troca de documentos, incluindo os "tops". Usando mesmo uma chave de apenas 128bits seria extremamente seguro, a chave pode ser de 128bits, 192bits ou 256bits.
Se você está desenvolvendo em Java EE 5 ou no Android (Ou qualquer Java 6, acredito eu, mas não confirmei), ambos já possuem a implementação deste algoritmo, caso a plataforma não possua você pode usar a implementação do Bouncy Castle, geralmente basta o Provider do bcprov-jdk16-*.jar .
O primeiro passo para a criptografia é definir uma chave, ela pode ser gerada automaticamente pelos mecanismos do sistema:
public byte[] key() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey key = keyGen.generateKey();
return key.getEncoded();
}
Esse método retorna o array de bytes de uma chave de 128bits, podemos usar 256bits mas esse algorismo nem sempre está disponível ou requer configurações especificas do JRE.
A criptografia trabalha com valores em Hexadecimal, assim a chave também usa valores em hexa, você pode criar uma chave com um array de hexas do tamanho que deseja da chave, uma chave de 128bits são 8 bytes (128/16), assim:
byte[] key = { 0x0f, 0xad, 0x54, 0x12, 0x00, 0xaf , 0x34, 0xff }Em posse de uma chave de criptografia basta aplica-la a uma engine de criptografia, junto dos bytes da mensagem a ser encriptada, com o seguinte método:
public byte[] encode(byte[] input, byte[] key) throws NoSuchAlgorithmException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, NoSuchPaddingException {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(input);
return encrypted;
}
Detalhe para o "AES/ECB/NoPadding" , isso usa o algoritmo AES e não faz o padding da mensagem.
Por trabalhar em hexadecimal, o algoritmo trabalha com blocos de 16 bytes, então a mensagem(seus bytes) devem ter uma tamanho múltiplo de 16 para não haver erros de "BadPaddingException: pad block corrupted, para isso podemos usar os seguintes métodos, que preenchem os espaços que faltam até formar o tamanho necessário com "null":
public String nullPadString(String original) {
StringBuffer output = new StringBuffer(original);
int remain = output.length() % 16;
if (remain != 0) {
remain = 16 - remain;
for (int i = 0; i < remain; i++) {
output.append((char) 0);
}
}
return output.toString();
}
Então podemos encriptar uma mensagem assim:
String mensagem = "Hello World!";
byte[] enc = encode(nullPadString(mensagem).getBytes(), key);
Para descriptografar a mensagem não tem mistério, basta o seguinte método:
public byte[] decode(byte[] input, byte[] key) throws NoSuchAlgorithmException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, NoSuchPaddingException {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(input);
return decrypted;
}
A única diferença é o parâmetro passado ao Cipher, para de descriptografar.
Agora que podemos criptografar e decodificar as mensagens, precisamos garantir seu formato ao ser enviado via HTTP/HTTPS.
Novamente, como trabalhamos com Hexadecimal, ele não é simplesmente convertido para String, não sem perda, e a conversão de volta também traria riscos, ainda mais se for em plataformas diferentes.
Precisa-se então converter esses valores hexadecimais para uma representação "por extenso" destes, e que possa ser transformado de volta, e podemos alcançar isto com os seguintes métodos:
public String fromHex(byte[] hex) {
StringBuffer sb = new StringBuffer();
for (int i=0; i < hex.length; i++) {
sb.append( Integer.toString( ( hex[i] & 0xff ) + 0x100, 16).substring( 1 ) );
}
return sb.toString();
}
public byte[] toHex(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
Então para enviar uma mensagem podemos proceder da seguinte forma (contando que ambos possuem a mesma chave):
String mensagem = "Hello World!";
byte[] enc = encode(nullPadString(mensagem).getBytes(), key);
String msgParaEnviar = fromHex(enc);
Enviamos então via http a string msgParaEnviar, e ao recebe-la procedemos a decodificação:
byte[] msgEnc = toHex(msgRecebida);
byte[] msg = decode(msgEnc,key);
String mensagem = new String(msg).trim();
Assim temos a mensagem nas duas pontas, o trim é necessário devido ao "padding".
Veja o código da Classe de Criptografia que uso.








