Algoritmo Criptografia AES Java compatível com C#

Recentemente tive de implementar um método de criptografia AES no Java de forma a ser compatível com a criptografia implementada em C# no back-end do cliente.

O código implementado em C# pelo cliente era análogo à este:

public static string Aes_EncryptString(string toEncrypt)
{
    try
    {
        string encrypted = null;
        AesManaged aes = null;
        try
        {
            var password = "hswPJNdopw5as0NJqweVCdA1FKsNYIlkdoVlk/poifW=";
            byte[] salt = Encoding.ASCII.GetBytes("ps85hjk98ng23ga");
            Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(password, salt);
            aes = new AesManaged();
            aes.Key = key.GetBytes(aes.KeySize / 8);
            aes.IV = key.GetBytes(aes.BlockSize / 8);
            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
            using (MemoryStream memory = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(memory, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        swEncrypt.Write(toEncrypt);
                    }
                }
                encrypted = Convert.ToBase64String(memory.ToArray());
            }
        }
        finally
        {
            if (aes != null) aes.Clear();
        }
        return encrypted;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

Pois bem tentei usar as classes de criptografia do Java mas nunca conseguia obter os mesmos resultados que o algoritmo escrito em C#.

O problema é que a implementação do PBEKeySpec do Java só suporta vetores de inicialização aleatórios e não pseudo-aleatórios como o C#, dessa forma nunca era possível obter a mesma encriptação como na implementação feita em C#.

A solução foi criar uma implementação do Rfc2898DeriveBytes em Java.
Essa implementação eu fiz como inner class de uma classe chamada AesCipher, a qual segue o código:

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * Algoritmo de enciptação/decriptação AES compatível com C# .Net.
 * 
 * @author ricardo.staroski
 */
public final class AesCipher {

    /**
     * Derivação de senha RFC 2898.<br>
     * Compatível com a classe <code>Rfc2898DeriveBytes</code> do C# .Net.
     */
    private static final class Rfc2898DeriveBytes {

        /**
         * Converte um <code>int</code> em um array de 4 <code>byte</code>s.
         * 
         * @param value O valor <code>int</code>.
         * @return Os 4 <code>byte</code>s que compõe o <code>int</code> informado.
         */
        private static byte[] intToBytes(int value) {
            return new byte[] { (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) (value >>> 0) };
        }

        private Mac hmacSha1;
        private byte[] salt;
        private byte[] buffer = new byte[20];

        private int iterationCount;
        private int bufferStartIndex = 0;
        private int bufferEndIndex = 0;
        private int block = 1;

        /**
         * @param password   A senha utilizada para derivar a chave.
         * @param salt       O salto utilizado para derivar a chave.
         * @param iterations O número de iterações da operação.
         * @throws NoSuchAlgorithmException Se o algoritmo HmacSHA1 núo estiver disponúvel.
         * @throws InvalidKeyException      Se o salto tiver menos de 8 bytes ou a senha for <code>null</code>.
         */
        public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations) throws NoSuchAlgorithmException, InvalidKeyException {
            if ((salt == null) || (salt.length < 8)) {
                throw new InvalidKeyException("Salt must be 8 bytes or more.");
            }
            if (password == null) {
                throw new InvalidKeyException("Password cannot be null.");
            }
            this.salt = salt;
            this.iterationCount = iterations;
            this.hmacSha1 = Mac.getInstance("HmacSHA1");
            this.hmacSha1.init(new SecretKeySpec(password, "HmacSHA1"));
        }

        /**
         * @param password A senha utilizada para derivar a chave.
         * @param salt     O salto utilizado para derivar a chave.
         * @throws NoSuchAlgorithmException     Se o algoritmo HmacSHA1 não estiver disponúvel.
         * @throws InvalidKeyException          Se o salto tiver menos de 8 bytes ou a senha for <code>null</code>.
         * @throws UnsupportedEncodingException Se o encoding UTF-8 não for suportado.
         */
        public Rfc2898DeriveBytes(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
            this(password, salt, 1000);
        }

        /**
         * @param password   A senha utilizada para derivar a chave.
         * @param salt       O salto utilizado para derivar a chave.
         * @param iterations O número de iteraçães da operação.
         * @throws NoSuchAlgorithmException     Se o algoritmo HmacSHA1 não estiver disponível.
         * @throws InvalidKeyException          Se o salto tiver menos de 8 bytes ou a senha for <code>null</code>.
         * @throws UnsupportedEncodingException Se o encoding UTF-8 não for suportado.
         */
        public Rfc2898DeriveBytes(String password, byte[] salt, int iterations) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
            this(password.getBytes("UTF8"), salt, iterations);
        }

        /**
         * Retorna uma chave pseudo-aleatória de uma senha, salto e iterações.
         * 
         * @param count Número de bytes para retornar.
         * @return O array de bytes.
         */
        public byte[] getBytes(int count) {
            byte[] result = new byte[count];
            int resultOffset = 0;
            int bufferCount = this.bufferEndIndex - this.bufferStartIndex;

            if (bufferCount > 0) {
                if (count < bufferCount) {
                    System.arraycopy(this.buffer, this.bufferStartIndex, result, 0, count);
                    this.bufferStartIndex += count;
                    return result;
                }
                System.arraycopy(this.buffer, this.bufferStartIndex, result, 0, bufferCount);
                this.bufferStartIndex = this.bufferEndIndex = 0;
                resultOffset += bufferCount;
            }

            while (resultOffset < count) {
                int needCount = count - resultOffset;
                this.buffer = this.func();
                if (needCount > 20) {
                    System.arraycopy(this.buffer, 0, result, resultOffset, 20);
                    resultOffset += 20;
                } else {
                    System.arraycopy(this.buffer, 0, result, resultOffset, needCount);
                    this.bufferStartIndex = needCount;
                    this.bufferEndIndex = 20;
                    return result;
                }
            }
            return result;
        }

        private byte[] func() {
            this.hmacSha1.update(this.salt, 0, this.salt.length);
            byte[] tempHash = this.hmacSha1.doFinal(intToBytes(this.block));

            this.hmacSha1.reset();
            byte[] finalHash = tempHash;
            for (int i = 2; i <= this.iterationCount; i++) {
                tempHash = this.hmacSha1.doFinal(tempHash);
                for (int j = 0; j < 20; j++) {
                    finalHash[j] = (byte) (finalHash[j] ^ tempHash[j]);
                }
            }
            if (this.block == 2147483647) {
                this.block = -2147483648;
            } else {
                this.block += 1;
            }
            return finalHash;
        }
    }

    private final SecretKeySpec key;
    private final IvParameterSpec iv;
    private final Cipher aesAlgorithm;

    /**
     * Construtor parametrizado.
     * 
     * @param password A senha a ser utilizada.
     * @param salt     O salto a ser utilizado.
     */
    public AesCipher(String password, String salt) {
        this(password, salt.getBytes());
    }

    /**
     * Construtor parametrizado.
     * 
     * @param password A senha a ser utilizada.
     * @param salt     O salto a ser utilizado.
     */
    public AesCipher(String password, byte[] salt) {
        try {
            aesAlgorithm = Cipher.getInstance("AES/CBC/PKCS5Padding");
            Rfc2898DeriveBytes deriveBytes = new Rfc2898DeriveBytes(password, salt);
            key = new SecretKeySpec(deriveBytes.getBytes(256 / 8), "AES");
            iv = new IvParameterSpec(deriveBytes.getBytes(128 / 8));
        } catch (Exception e) {
            throw new RuntimeException(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Decripta a {@link String} informada.
     * 
     * @param toDecrypt {@link String} a ser decriptada.
     * @return A {@link String} decriptada.
     */
    public String decrypt(String toDecrypt) {
        try {
            aesAlgorithm.init(Cipher.DECRYPT_MODE, key, iv);
            byte[] encoded = toDecrypt.getBytes();
            byte[] encrypted = Base64.getDecoder().decode(encoded);
            byte[] decrypted = aesAlgorithm.doFinal(encrypted);
            return new String(decrypted);
        } catch (Exception e) {
            throw new RuntimeException(e.getLocalizedMessage(), e);
        }
    }

    /**
     * Encripta a {@link String} informada.
     * 
     * @param toEncrypt {@link String} a ser encriptada.
     * @return A {@link String} encriptada.
     */
    public String encrypt(String toEncrypt) {
        try {
            aesAlgorithm.init(Cipher.ENCRYPT_MODE, key, iv);
            byte[] decrypted = toEncrypt.getBytes();
            byte[] encrypted = aesAlgorithm.doFinal(decrypted);
            byte[] encoded = Base64.getEncoder().encode(encrypted);
            return new String(encoded);
        } catch (Exception e) {
            throw new RuntimeException(e.getLocalizedMessage(), e);
        }
    }
}

Com a classe classe AesCipher consegui reproduzir a funcionalidade do código C# da seguinte forma no Java:

public static String aesEncryptString(String toEncrypt) {
    String password = "hswPJNdopw5as0NJqweVCdA1FKsNYIlkdoVlk/poifW=";
    byte[] salt = "ps85hjk98ng23ga".getBytes();
    AesCipher aes = new AesCipher(password, salt);
    String encrypted = aes.encrypt(toEncrypt);
    return encrypted;
}

Como essa implementação me trouxe uma certa dor de cabeça, resolvi compartilhá-la com a comunidade, caso alguém passe pelo mesmo problema futuramente.
:slight_smile:

6 curtidas

Cara você me ajudou muito! Manda o seu PIX por favor, quero agraceder kkkkkk
:star_struck: :rofl:

1 curtida

Sério? Quer pagar quanto?
:rofl:

Sério rsrs. R$100 pela sua boa vontade.

1 curtida

Faz o seguinte:
Procura alguma vaquinha online de alguém precisando de ajuda.
Já tenho minha atividade remunerada, se você ajudar alguém em necessidade vai ser bem melhor.
:pray:

Tudo bem. Doação realizada: Doação realizada! - Album on Imgur
Mais uma vez, obrigado por compartilhar. :clap:

1 curtida