AES Calculator
Encrypt and decrypt data using AES-128, AES-192 or AES-256 in CBC or GCM mode — powered by the browser WebCrypto API, so your key material never leaves this page.
Use test data only. All calculations run locally in your browser — PayProbe never sees, transmits, or stores your PAN, CVV, keys, PINs, or cryptographic inputs. How we handle data →
Inputs
About AES modes
AES-CBC
Cipher Block Chaining — each plaintext block is XORed with the previous ciphertext block before encryption. Requires a random, unpredictable IV and provides only confidentiality. Padding oracle attacks are possible if unauthenticated; always pair with a MAC (e.g. HMAC-SHA256) in production. Common in EMV/payment HSM contexts (ISO 9797, PIN blocks, key diversification).
AES-GCM
Galois/Counter Mode — authenticated encryption with associated data (AEAD). Provides both confidentiality and integrity/authenticity via a 128-bit auth tag. Uses a 12-byte nonce; never reuse a nonce with the same key — doing so fatally compromises both confidentiality and authenticity. Preferred for TLS 1.3, API payloads and any new protocol.
Why no ECB?
Electronic Codebook mode encrypts each block independently, so identical plaintext blocks produce identical ciphertext blocks. This leaks structural information (the classic "ECB penguin" problem) and is trivially distinguishable from random. ECB must never be used in production; it is intentionally absent from this tool.
Key sizes
AES-128: 16-byte key — adequate for most use-cases, fast on all hardware.
AES-192: 24-byte key — rare, not used in TLS or most standards.
AES-256: 32-byte key — required by some compliance frameworks (PCI HSM, NIST post-quantum guidance). All three have the same block size (16 bytes) and differ only in key schedule rounds (10 / 12 / 14).
# AES-CBC and AES-GCM encrypt/decrypt
# Requires: pip install pycryptodome
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
def aes_cbc_encrypt(key: bytes, iv: bytes, plaintext: bytes) -> bytes:
cipher = AES.new(key, AES.MODE_CBC, iv)
return cipher.encrypt(pad(plaintext, AES.block_size))
def aes_cbc_decrypt(key: bytes, iv: bytes, ciphertext: bytes) -> bytes:
cipher = AES.new(key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(ciphertext), AES.block_size)
def aes_gcm_encrypt(key: bytes, nonce: bytes, plaintext: bytes) -> tuple:
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
return ciphertext, tag
def aes_gcm_decrypt(key: bytes, nonce: bytes, ciphertext: bytes, tag: bytes) -> bytes:
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
return cipher.decrypt_and_verify(ciphertext, tag)
# Example — AES-128-CBC
key = bytes.fromhex("2B7E151628AED2A6ABF7158809CF4F3C")
iv = bytes.fromhex("00000000000000000000000000000000")
plain = bytes.fromhex("6BC1BEE22E409F96E93D7E117393172A")
enc = aes_cbc_encrypt(key, iv, plain)
dec = aes_cbc_decrypt(key, iv, enc)
print(f"Encrypted : {enc.hex().upper()}") # 7649ABAC8119B246CEE98E9B12E9197D...
print(f"Decrypted : {dec.hex().upper()}") # 6BC1BEE22E409F96E93D7E117393172A