Python AES Encryption Code Example (Online Runner)

Python AES examples with mode, padding, key size, and encoding controls to mirror the AES tool.

Online calculator: use the site AES tool.

Note: This snippet requires locally installed dependencies and will not run in the online runner.

Calculation method

The AES tool supports ECB/CBC/CFB/OFB/CTR modes, PKCS7 or zero padding (for block modes), key size selection, and hex or base64 ciphertext encodings. This helper mirrors those options using PyCryptodome.

Install the dependency first: pip install pycryptodome.

Implementation notes

  • Package: pycryptodome provides Crypto.Cipher.AES and Crypto.Util.Counter.
  • Implementation: keys are normalized to the selected byte length (16/24/32) to match the tool, and IVs are padded or truncated to 16 bytes. CTR builds its counter from the IV bytes so encryption and decryption must reuse the same IV.
  • Notes: padding only applies to ECB/CBC. Zero padding is ambiguous if plaintext ends with \x00; prefer PKCS7 unless you control both ends. For real usage generate a random IV per message and store it alongside the ciphertext.
python
from __future__ import annotations

import base64
from typing import Literal

from Crypto.Cipher import AES
from Crypto.Util import Counter

Mode = Literal["ECB", "CBC", "CFB", "OFB", "CTR"]
Padding = Literal["PKCS7", "Zero"]
KeyEncoding = Literal["utf8", "hex"]
CipherEncoding = Literal["hex", "base64"]

BLOCK_SIZE = 16


def _normalize_key(key: bytes, key_size: int) -> bytes:
    padded = key[:key_size].ljust(key_size, b"\x00")
    return padded


def _normalize_iv(iv: bytes | None) -> bytes:
    return (iv or b"")[:BLOCK_SIZE].ljust(BLOCK_SIZE, b"\x00")


def _decode_value(value: str, encoding: KeyEncoding) -> bytes:
    return value.encode("utf-8") if encoding == "utf8" else bytes.fromhex(value)


def _encode_ciphertext(data: bytes, encoding: CipherEncoding) -> str:
    return data.hex() if encoding == "hex" else base64.b64encode(data).decode("ascii")


def _decode_ciphertext(value: str, encoding: CipherEncoding) -> bytes:
    return bytes.fromhex(value) if encoding == "hex" else base64.b64decode(value)


def _pkcs7_pad(data: bytes) -> bytes:
    pad_len = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
    return data + bytes([pad_len] * pad_len)


def _pkcs7_unpad(data: bytes) -> bytes:
    if not data:
        return data
    pad_len = data[-1]
    if pad_len < 1 or pad_len > BLOCK_SIZE:
        raise ValueError("Invalid PKCS7 padding")
    if data[-pad_len:] != bytes([pad_len] * pad_len):
        raise ValueError("Invalid PKCS7 padding")
    return data[:-pad_len]


def _zero_pad(data: bytes) -> bytes:
    if len(data) % BLOCK_SIZE == 0:
        return data
    pad_len = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
    return data + b"\x00" * pad_len


def _zero_unpad(data: bytes) -> bytes:
    return data.rstrip(b"\x00")


def _build_cipher(key: bytes, mode: Mode, iv: bytes | None) -> AES:
    if mode == "ECB":
        return AES.new(key, AES.MODE_ECB)
    if mode == "CBC":
        return AES.new(key, AES.MODE_CBC, iv=_normalize_iv(iv))
    if mode == "CFB":
        return AES.new(key, AES.MODE_CFB, iv=_normalize_iv(iv), segment_size=128)
    if mode == "OFB":
        return AES.new(key, AES.MODE_OFB, iv=_normalize_iv(iv))
    counter = Counter.new(128, initial_value=int.from_bytes(_normalize_iv(iv), "big"))
    return AES.new(key, AES.MODE_CTR, counter=counter)


def aes_encrypt(
    plaintext: str,
    key: str,
    *,
    mode: Mode = "CBC",
    padding: Padding = "PKCS7",
    key_encoding: KeyEncoding = "hex",
    iv: str | None = None,
    iv_encoding: KeyEncoding = "hex",
    key_size: int = 32,
    output_encoding: CipherEncoding = "base64",
) -> str:
    key_bytes = _normalize_key(_decode_value(key, key_encoding), key_size)
    iv_bytes = _decode_value(iv, iv_encoding) if iv is not None else None
    data = plaintext.encode("utf-8")

    if mode in {"ECB", "CBC"}:
        data = _pkcs7_pad(data) if padding == "PKCS7" else _zero_pad(data)

    cipher = _build_cipher(key_bytes, mode, iv_bytes)
    encrypted = cipher.encrypt(data)
    return _encode_ciphertext(encrypted, output_encoding)


def aes_decrypt(
    ciphertext: str,
    key: str,
    *,
    mode: Mode = "CBC",
    padding: Padding = "PKCS7",
    key_encoding: KeyEncoding = "hex",
    iv: str | None = None,
    iv_encoding: KeyEncoding = "hex",
    key_size: int = 32,
    input_encoding: CipherEncoding = "base64",
) -> str:
    key_bytes = _normalize_key(_decode_value(key, key_encoding), key_size)
    iv_bytes = _decode_value(iv, iv_encoding) if iv is not None else None
    data = _decode_ciphertext(ciphertext, input_encoding)
    cipher = _build_cipher(key_bytes, mode, iv_bytes)
    decrypted = cipher.decrypt(data)
    if mode in {"ECB", "CBC"}:
        decrypted = _pkcs7_unpad(decrypted) if padding == "PKCS7" else _zero_unpad(decrypted)
    return decrypted.decode("utf-8", errors="replace")

# Example usage
key_hex = "00112233445566778899aabbccddeeff"
iv_hex = "0102030405060708090a0b0c0d0e0f10"

cipher = aes_encrypt(
    "hello",
    key_hex,
    mode="CBC",
    padding="PKCS7",
    key_encoding="hex",
    iv=iv_hex,
    iv_encoding="hex",
    key_size=16,
    output_encoding="hex",
)
print(cipher)

plain = aes_decrypt(
    cipher,
    key_hex,
    mode="CBC",
    padding="PKCS7",
    key_encoding="hex",
    iv=iv_hex,
    iv_encoding="hex",
    key_size=16,
    input_encoding="hex",
)
print(plain)

Complete script (implementation + tests)

python
from __future__ import annotations

import base64
from typing import Literal

from Crypto.Cipher import AES
from Crypto.Util import Counter

Mode = Literal["ECB", "CBC", "CFB", "OFB", "CTR"]
Padding = Literal["PKCS7", "Zero"]
KeyEncoding = Literal["utf8", "hex"]
CipherEncoding = Literal["hex", "base64"]

BLOCK_SIZE = 16


def _normalize_key(key: bytes, key_size: int) -> bytes:
    padded = key[:key_size].ljust(key_size, b"\x00")
    return padded


def _normalize_iv(iv: bytes | None) -> bytes:
    return (iv or b"")[:BLOCK_SIZE].ljust(BLOCK_SIZE, b"\x00")


def _decode_value(value: str, encoding: KeyEncoding) -> bytes:
    return value.encode("utf-8") if encoding == "utf8" else bytes.fromhex(value)


def _encode_ciphertext(data: bytes, encoding: CipherEncoding) -> str:
    return data.hex() if encoding == "hex" else base64.b64encode(data).decode("ascii")


def _decode_ciphertext(value: str, encoding: CipherEncoding) -> bytes:
    return bytes.fromhex(value) if encoding == "hex" else base64.b64decode(value)


def _pkcs7_pad(data: bytes) -> bytes:
    pad_len = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
    return data + bytes([pad_len] * pad_len)


def _pkcs7_unpad(data: bytes) -> bytes:
    if not data:
        return data
    pad_len = data[-1]
    if pad_len < 1 or pad_len > BLOCK_SIZE:
        raise ValueError("Invalid PKCS7 padding")
    if data[-pad_len:] != bytes([pad_len] * pad_len):
        raise ValueError("Invalid PKCS7 padding")
    return data[:-pad_len]


def _zero_pad(data: bytes) -> bytes:
    if len(data) % BLOCK_SIZE == 0:
        return data
    pad_len = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
    return data + b"\x00" * pad_len


def _zero_unpad(data: bytes) -> bytes:
    return data.rstrip(b"\x00")


def _build_cipher(key: bytes, mode: Mode, iv: bytes | None) -> AES:
    if mode == "ECB":
        return AES.new(key, AES.MODE_ECB)
    if mode == "CBC":
        return AES.new(key, AES.MODE_CBC, iv=_normalize_iv(iv))
    if mode == "CFB":
        return AES.new(key, AES.MODE_CFB, iv=_normalize_iv(iv), segment_size=128)
    if mode == "OFB":
        return AES.new(key, AES.MODE_OFB, iv=_normalize_iv(iv))
    counter = Counter.new(128, initial_value=int.from_bytes(_normalize_iv(iv), "big"))
    return AES.new(key, AES.MODE_CTR, counter=counter)


def aes_encrypt(
    plaintext: str,
    key: str,
    *,
    mode: Mode = "CBC",
    padding: Padding = "PKCS7",
    key_encoding: KeyEncoding = "hex",
    iv: str | None = None,
    iv_encoding: KeyEncoding = "hex",
    key_size: int = 32,
    output_encoding: CipherEncoding = "base64",
) -> str:
    key_bytes = _normalize_key(_decode_value(key, key_encoding), key_size)
    iv_bytes = _decode_value(iv, iv_encoding) if iv is not None else None
    data = plaintext.encode("utf-8")

    if mode in {"ECB", "CBC"}:
        data = _pkcs7_pad(data) if padding == "PKCS7" else _zero_pad(data)

    cipher = _build_cipher(key_bytes, mode, iv_bytes)
    encrypted = cipher.encrypt(data)
    return _encode_ciphertext(encrypted, output_encoding)


def aes_decrypt(
    ciphertext: str,
    key: str,
    *,
    mode: Mode = "CBC",
    padding: Padding = "PKCS7",
    key_encoding: KeyEncoding = "hex",
    iv: str | None = None,
    iv_encoding: KeyEncoding = "hex",
    key_size: int = 32,
    input_encoding: CipherEncoding = "base64",
) -> str:
    key_bytes = _normalize_key(_decode_value(key, key_encoding), key_size)
    iv_bytes = _decode_value(iv, iv_encoding) if iv is not None else None
    data = _decode_ciphertext(ciphertext, input_encoding)
    cipher = _build_cipher(key_bytes, mode, iv_bytes)
    decrypted = cipher.decrypt(data)
    if mode in {"ECB", "CBC"}:
        decrypted = _pkcs7_unpad(decrypted) if padding == "PKCS7" else _zero_unpad(decrypted)
    return decrypted.decode("utf-8", errors="replace")


def run_tests() -> None:
    key = "00112233445566778899aabbccddeeff"
    iv = "0102030405060708090a0b0c0d0e0f10"
    cipher = aes_encrypt(
        "hello",
        key,
        mode="CBC",
        padding="PKCS7",
        key_encoding="hex",
        iv=iv,
        iv_encoding="hex",
        key_size=16,
        output_encoding="hex",
    )
    recovered = aes_decrypt(
        cipher,
        key,
        mode="CBC",
        padding="PKCS7",
        key_encoding="hex",
        iv=iv,
        iv_encoding="hex",
        key_size=16,
        input_encoding="hex",
    )
    assert recovered == "hello"
    print("AES tests passed")


if __name__ == "__main__":
    run_tests()