import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Crypto {
// Hash data
public static final int HASH_TYPE_MD5 = 1;
public static final int HASH_TYPE_SHA1 = 2;
public static final int HASH_LENGTH_MD5 = 16;
public static final int HASH_LENGTH_SHA1 = 20;
private final String HASH_ALGORITHM_NAME_MD5 = "MD5";
private final String HASH_ALGORITHM_NAME_SHA1 = "SHA-1";
// Crypt data
public static final int CRYPT_TYPE_DES = 3;
public static final int CRYPT_TYPE_3DES = 4;
public static final int CRYPT_TYPE_RC2 = 5;
public static final int CRYPT_TYPE_RC4 = 6;
private final String CRYPT_ALGORITHM_NAME_DES = "DES";
private final String CRYPT_ALGORITHM_NAME_3DES = "DESede";
private final String CRYPT_ALGORITHM_NAME_RC2 = "RC2";
private final String CRYPT_ALGORITHM_NAME_RC4 = "RC4";
private final int CRYPT_KEY_LENGTH_DES = 8;
private final int CRYPT_KEY_LENGTH_3DES = 24; // 168 bits + parity
private final int CRYPT_KEY_LENGTH_RC2RC4 = 16; // 128 bits + parity
public byte[] calcHash(byte[] data, int algorithmType) throws NoSuchAlgorithmException {
MessageDigest md = null;
if (HASH_TYPE_MD5 == algorithmType)
md = MessageDigest.getInstance(HASH_ALGORITHM_NAME_MD5);
else if (HASH_TYPE_SHA1 == algorithmType)
md = MessageDigest.getInstance(HASH_ALGORITHM_NAME_SHA1);
else
throw new NoSuchAlgorithmException();
md.update(data);
byte bufferSaida[] = md.digest();
return bufferSaida;
}
public byte[] crypt(byte[] data, String key, int cryptAlgorithmType) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
// Valor padrão para hash de chave no BSCripto
return crypt(data, key, cryptAlgorithmType, HASH_TYPE_SHA1);
}
public byte[] crypt(byte[] data, String key, int cryptAlgorithmType, int keyHashAlgorithmType) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
return cryptDecrypt(data, key, cryptAlgorithmType, keyHashAlgorithmType, Cipher.ENCRYPT_MODE);
}
public byte[] decrypt(byte[] data, String key, int cryptAlgorithmType) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
// Valor padrão para hash de chave no BSCripto
return crypt(data, key, cryptAlgorithmType, HASH_TYPE_SHA1);
}
public byte[] decrypt(byte[] data, String key, int cryptAlgorithmType, int keyHashAlgorithmType) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
return cryptDecrypt(data, key, cryptAlgorithmType, keyHashAlgorithmType, Cipher.DECRYPT_MODE);
}
private byte[] cryptDecrypt(byte[] data, String key, int cryptAlgorithmType, int hashAlgorithmType, int encryptMode) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalStateException, IllegalBlockSizeException, BadPaddingException {
byte[] hashKey = null;
Cipher cipher = null;
String algorithmName = getCryptAlgorithmName(cryptAlgorithmType);
// Obtém o hash da chave
hashKey = calcHash(key.getBytes(), hashAlgorithmType);
// Obtém uma chave com o tamanho correto para o algoritmo de criptografia escolhido
byte[] derivedKey = cryptDeriveKey(hashKey, cryptAlgorithmType, hashAlgorithmType);
// Gera um vetor de inicialização zerado igual ao utilizado na CryptoAPI
byte[] iv = new byte[8];
Arrays.fill(iv, (byte) 0x00);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// Gera a chave secreta para o algoritmo de criptografia escolhido
SecretKeySpec secretKeySpec = new SecretKeySpec(derivedKey, algorithmName);
// Criptografa os dados
cipher = Cipher.getInstance(algorithmName + "/CBC/PKCS5Padding");
cipher.init(encryptMode, secretKeySpec, ivParameterSpec);
byte[] cryptData = cipher.doFinal(data);
return cryptData;
}
private byte[] cryptDeriveKey(byte[] key, int cryptAlgorithmType, int hashAlgorithmType) throws NoSuchAlgorithmException {
byte[] derivedKey;
switch (cryptAlgorithmType) {
case CRYPT_TYPE_3DES :
derivedKey = cryptDeriveKey(key, CRYPT_KEY_LENGTH_3DES, hashAlgorithmType, true);
break;
case CRYPT_TYPE_DES :
derivedKey = cryptDeriveKey(key, CRYPT_KEY_LENGTH_DES, hashAlgorithmType, true);
break;
case CRYPT_TYPE_RC2 :
case CRYPT_TYPE_RC4 :
derivedKey = cryptDeriveKey(key, CRYPT_KEY_LENGTH_RC2RC4, hashAlgorithmType, true);
break;
default :
throw new NoSuchAlgorithmException();
}
return derivedKey;
}
private byte[] cryptDeriveKey(byte[] key, int keySize, int hashAlgorithmType, boolean parity) throws NoSuchAlgorithmException {
byte[] buffer1 = new byte[64];
byte[] buffer2 = new byte[64];
byte[] derivedKey = new byte[keySize];
// Caso não necessite de derivação retorna a chave sem derivação.
// A derivação é feita apenas quando o tamanho da chave para o algoritmo
// de criptografia escolhido é maior que a chave recebida
if (((HASH_TYPE_MD5 == hashAlgorithmType) && (keySize <= HASH_LENGTH_MD5)) || ((HASH_TYPE_SHA1 == hashAlgorithmType) && (keySize <= HASH_LENGTH_SHA1))) {
for (int i = 0; i < keySize; i++)
derivedKey[i] = key[i];
return derivedKey;
}
// Preenche os buffers
Arrays.fill(buffer1, (byte) 0x36);
Arrays.fill(buffer2, (byte) 0x5C);
// Faz o XOR da chave com os buffers
for (int i = 0; i < key.length; i++) {
buffer1[i] ^= key[i];
buffer2[i] ^= key[i];
}
// Calcula o hash utilizando o mesmo algoritmo utilizado na chave passada
buffer1 = calcHash(buffer1, hashAlgorithmType);
buffer2 = calcHash(buffer2, hashAlgorithmType);
// Transporta os bytes dos buffers para a chave final até o tamanho desejado
for (int i = 0; i < keySize; i++) {
if (i < buffer1.length)
derivedKey[i] = buffer1[i];
else
derivedKey[i] = buffer2[i - buffer1.length];
}
// Adiciona paridade
if (true == parity)
return addParityBit(derivedKey);
else
return derivedKey;
}
private byte[] addParityBit(byte[] key) {
byte[] keyData = new byte[key.length];
for (short i = 0; i < key.length; i++) {
byte b = key[i];
byte paridade = (byte) ((b >> 1) ^ (b >> 2) ^ (b >> 3) ^ (b >> 4) ^ (b >> 5) ^ (b >> 6) ^ (b >> 7));
keyData[i] = (byte) ((b & 0xFE) | (paridade & 0x01));
}
return keyData;
}
private String getCryptAlgorithmName(int cryptAlgorithmType) throws NoSuchAlgorithmException {
String algorithmName;
switch (cryptAlgorithmType) {
case CRYPT_TYPE_3DES :
algorithmName = CRYPT_ALGORITHM_NAME_3DES;
break;
case CRYPT_TYPE_DES :
algorithmName = CRYPT_ALGORITHM_NAME_DES;
break;
case CRYPT_TYPE_RC2 :
algorithmName = CRYPT_ALGORITHM_NAME_RC2;
break;
case CRYPT_TYPE_RC4 :
algorithmName = CRYPT_ALGORITHM_NAME_RC4;
break;
default :
throw new NoSuchAlgorithmException();
}
return algorithmName;
}
}