Implementação de Criptografia AES em C#, Java, PHP, Python e JavaScript

Para que diferentes plataformas troquem dados cifrados de forma consistente, é preciso alinhar modo de operação, tamanho de chave, vetor de inicialização, esquema de preenchimento e codificação dos bytes. O cenário mais comum é o AES-128 no modo CBC com Zero Padding (ou No Padding) e strings codificadas em UTF-8. A seguir, veja exemplos funcionais em cinco linguagens.

C#

No .NET, a classe RijndaelManaged permite configurar chave, IV, modo e preenchimento. O exemplo abaixo aplica o preenchimento manualmente e converte o resultado para Base64.

using System;
using System.Security.Cryptography;
using System.Text;

public class AesCipher
{
    private static readonly int BlockSize = 16;

    public static void Main()
    {
        string text = "Texto de exemplo";
        string key = "chave12345678901";
        string iv = "vetorinicial1234";

        string encoded = Encrypt(text, key, iv);
        Console.WriteLine(encoded);

        string decoded = Decrypt(encoded, key, iv);
        Console.WriteLine(decoded);
    }

    public static string Encrypt(string plain, string key, string iv)
    {
        byte[] keyBytes = Encoding.UTF8.GetBytes(key);
        byte[] ivBytes = Encoding.UTF8.GetBytes(iv);
        byte[] plainBytes = Pad(Encoding.UTF8.GetBytes(plain));

        using (RijndaelManaged aes = new RijndaelManaged())
        {
            aes.Key = keyBytes;
            aes.IV = ivBytes;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.Zeros;
            ICryptoTransform transform = aes.CreateEncryptor();
            byte[] result = transform.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
            return Convert.ToBase64String(result);
        }
    }

    public static string Decrypt(string encoded, string key, string iv)
    {
        byte[] keyBytes = Encoding.UTF8.GetBytes(key);
        byte[] ivBytes = Encoding.UTF8.GetBytes(iv);
        byte[] cipherBytes = Convert.FromBase64String(encoded);

        using (RijndaelManaged aes = new RijndaelManaged())
        {
            aes.Key = keyBytes;
            aes.IV = ivBytes;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.Zeros;
            ICryptoTransform transform = aes.CreateDecryptor();
            byte[] result = transform.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
            return Encoding.UTF8.GetString(result).TrimEnd('\0');
        }
    }

    private static byte[] Pad(byte[] data)
    {
        int remainder = data.Length % BlockSize;
        if (remainder == 0) return data;
        int newSize = data.Length + (BlockSize - remainder);
        Array.Resize(ref data, newSize);
        return data;
    }
}

Java

Em Java, a transformação AES/CBC/NoPadding exige que o bloco já tenha tamanho múltiplo de 16 bytes. A decoddificação Base64 pode usar java.util.Base64.

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AesExample {

    public static void main(String[] args) throws Exception {
        String text = "Texto de exemplo";
        String key = "chave12345678901";
        String iv = "vetorinicial1234";

        String encrypted = encrypt(text, key, iv);
        System.out.println(encrypted);

        String decrypted = decrypt(encrypted, key, iv);
        System.out.println(decrypted);
    }

    public static String encrypt(String plain, String key, String iv) throws Exception {
        byte[] input = fillBlock(plain.getBytes("UTF-8"));
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes("UTF-8"));

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
        byte[] output = cipher.doFinal(input);
        return Base64.getEncoder().encodeToString(output);
    }

    public static String decrypt(String encoded, String key, String iv) throws Exception {
        byte[] input = Base64.getDecoder().decode(encoded);
        SecretKeySpec keySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
        IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes("UTF-8"));

        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
        byte[] output = cipher.doFinal(input);
        return new String(output, "UTF-8").replaceAll("\\x00+$", "");
    }

    private static byte[] fillBlock(byte[] data) {
        int block = 16;
        int rest = data.length % block;
        if (rest == 0) return data;
        byte[] padded = new byte[data.length + (block - rest)];
        System.arraycopy(data, 0, padded, 0, data.length);
        return padded;
    }
}

PHP

Como o OpenSSL do PHP não oferece preenchimento com zeros diretamente, o texto precisa ser completado antes da cifragem e ajustado após a decifragem.

<?php
function zeroPad($data) {
    $block = 16;
    $len = strlen($data);
    $rest = $len % $block;
    if ($rest === 0) return $data;
    return $data . str_repeat("\0", $block - $rest);
}

$key = "chave12345678901";
$iv = "vetorinicial1234";
$text = "Texto de exemplo";

$encrypted = openssl_encrypt(
    zeroPad($text),
    "AES-128-CBC",
    $key,
    OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
    $iv
);
$encoded = base64_encode($encrypted);
echo $encoded . PHP_EOL;

$decoded = base64_decode($encoded);
$decrypted = openssl_decrypt(
    $decoded,
    "AES-128-CBC",
    $key,
    OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING,
    $iv
);
echo rtrim($decrypted, "\0") . PHP_EOL;
?>

JavaScript

A biblioteca crypto-js já inclui o preenchimento ZeroPadding, o que torna a operação mais direta no navegador ou no Node.js.

<script src="aes.js"></script>
<script src="pad-zeropadding.js"></script>
<script>
    var text = "Texto de exemplo";
    var key = CryptoJS.enc.Utf8.parse("chave12345678901");
    var iv = CryptoJS.enc.Utf8.parse("vetorinicial1234");

    var encrypted = CryptoJS.AES.encrypt(text, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.ZeroPadding
    });

    console.log(encrypted.toString());

    var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
        iv: iv,
        padding: CryptoJS.pad.ZeroPadding
    });

    console.log(decrypted.toString(CryptoJS.enc.Utf8));
</script>

Python

O exemplo usa a biblioteca pycryptodome. O objeto de cifrgaem deve ser recriado para a decifragem, e o preenchimento deve ser removido com rstrip.

from Crypto.Cipher import AES
import base64

BLOCK = 16
FILL = b'\0'

def pad(s):
    return s + (BLOCK - len(s) % BLOCK) * FILL

def unpad(s):
    return s.rstrip(FILL)

key = b'chave12345678901'
iv = b'vetorinicial1234'
source = b'Texto de exemplo'

engine = AES.new(key, AES.MODE_CBC, iv)
cipher = engine.encrypt(pad(source))
encoded = base64.b64encode(cipher)
print(encoded.decode('utf-8'))

engine = AES.new(key, AES.MODE_CBC, iv)
plain = unpad(engine.decrypt(cipher))
print(plain.decode('utf-8'))

Compatibilidade entre as linguagens

Para que todos os exemplos produzam o mesmo resultado, mantenha os seguintes parâmetros iguais em cada lado: algoritmo AES, modo CBC, chave e IV de 16 bytes, preenchimento com \0, codificação UTF-8 e representação Base64 para trensporte. Quando a API não suporta ZeroPadding explicitamente, use NoPadding e preencha o bloco manualmente.

Tags: AES C# java PHP Python

Publicado em 6-25 20:31