UUID Studio

AES-CBC Encrypt

AES-CBC with passphrase-derived keys.

  • 🔒 No data stored or uploaded
  • âš¡ 100% client-side
  • 🆓 Free, no account

Need more than one tool at a time? Open the full Workbench - or press Ctrl+K to jump to any tool.

Hash, HMAC, AES-GCM/CBC + RSA-OAEP, codecs, JWT decoding, UUID v4, and secure random - all client-side. JWTs use Base64URL (three segments), not a single MIME Base64 block - use Decode JWT below, not raw Base64 decode.

About AES-CBC Encrypt

AES is a symmetric block cipher: the same key encrypts, and anyone holding the key can reverse the operation. AES itself only transforms one 128-bit block, so the mode of operation - here AES-CBC - is what determines how a message of any length is handled, and it matters far more to the security of the result than the key size does.

AES-CBC chains blocks together: each plaintext block is XORed with the previous ciphertext block before encryption, starting from a 128-bit initialisation vector. It provides confidentiality only - there is no built-in integrity check, so CBC on its own cannot tell you whether a ciphertext was modified.

You will mostly meet CBC in existing systems: an older API, a database column, a payment integration, a file format that predates AEAD. It is still perfectly good at hiding data when used correctly, which is why so much of it is still in production.

Because there is no authentication, a modified ciphertext still decrypts - into different plaintext. Combined with PKCS#7 padding this enables padding-oracle attacks, where an attacker who can distinguish a padding error from other errors recovers the plaintext without the key. If you must use CBC, add an HMAC over the IV and ciphertext and verify it before decrypting (encrypt-then-MAC), or move to AES-GCM.

Everything on this page happens in your browser through the Web Crypto API. Keys, plaintext and ciphertext are never uploaded, which is what makes it safe to encrypt a real value here while you are debugging.

How to use the AES-CBC Encrypt

  1. Paste the plaintext you want to encrypt.
  2. Provide a key of the right length - 16, 24 or 32 bytes for AES-128, AES-192 or AES-256. Decode it from hex or Base64 first if that is how it is stored.
  3. A random IV of 16 bytes is generated for you. It must be unpredictable, not a fixed constant.
  4. Copy the ciphertext together with the IV. You cannot decrypt later without both.

Examples

  • Plaintext
    legacy payload

AES-CBC Encrypt in code

The same operation this tool performs, in the languages you are most likely to need it.

JavaScript (browser / Web Crypto)
const enc = new TextEncoder();
const key = await crypto.subtle.generateKey(
  { name: "AES-CBC", length: 256 }, true, ["encrypt", "decrypt"]
);
const iv = crypto.getRandomValues(new Uint8Array(16));

const ct = await crypto.subtle.encrypt(
  { name: "AES-CBC", iv }, key, enc.encode("secret message")
);

// CBC has no integrity check. In real code, HMAC the IV+ciphertext
// and verify that tag BEFORE calling decrypt (encrypt-then-MAC).
const pt = await crypto.subtle.decrypt({ name: "AES-CBC", iv }, key, ct);
console.log(new TextDecoder().decode(pt));
Node.js
import { randomBytes, createCipheriv, createDecipheriv, createHmac, timingSafeEqual } from "node:crypto";

const key = randomBytes(32);
const macKey = randomBytes(32);         // separate key for the MAC
const iv = randomBytes(16);

const c = createCipheriv("aes-256-cbc", key, iv);
const ct = Buffer.concat([c.update("secret message", "utf8"), c.final()]);

// Encrypt-then-MAC: authenticate the IV and ciphertext together.
const mac = createHmac("sha256", macKey).update(iv).update(ct).digest();

// On the way back, verify the MAC first and only then decrypt.
const expected = createHmac("sha256", macKey).update(iv).update(ct).digest();
if (!timingSafeEqual(mac, expected)) throw new Error("tampered");
const d = createDecipheriv("aes-256-cbc", key, iv);
console.log(Buffer.concat([d.update(ct), d.final()]).toString("utf8"));
Python (cryptography)
import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding

key, iv = os.urandom(32), os.urandom(16)

padder = padding.PKCS7(128).padder()
data = padder.update(b"secret message") + padder.finalize()
enc = Cipher(algorithms.AES(key), modes.CBC(iv)).encryptor()
ct = enc.update(data) + enc.finalize()

dec = Cipher(algorithms.AES(key), modes.CBC(iv)).decryptor()
unpadder = padding.PKCS7(128).unpadder()
pt = unpadder.update(dec.update(ct) + dec.finalize()) + unpadder.finalize()
print(pt)
# Prefer AESGCM unless a legacy format forces CBC on you.
Java
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

byte[] keyBytes = new byte[32], iv = new byte[16];
SecureRandom rng = new SecureRandom();
rng.nextBytes(keyBytes); rng.nextBytes(iv);

SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
c.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] ct = c.doFinal("secret message".getBytes("UTF-8"));

// Never use "AES" alone as the transformation - it silently means
// AES/ECB/PKCS5Padding, which leaks repeated plaintext blocks.
OpenSSL
# Encrypt (key and IV as hex, no salt/KDF so it matches library output)
openssl enc -aes-256-cbc -K "$KEY_HEX" -iv "$IV_HEX" \
  -in plain.txt -out cipher.bin

# Decrypt
openssl enc -d -aes-256-cbc -K "$KEY_HEX" -iv "$IV_HEX" \
  -in cipher.bin -out plain.txt

When you need this

  • Producing a AES-CBC test vector for an integration that expects it.
  • Checking that your application's ciphertext matches another system's for the same key and IV.
  • Seeing how the IV changes the ciphertext for identical plaintext.
  • Working out the exact byte layout to store - ciphertext, IV and MAC.
  • Confirming a key you were given is the right length before wiring it in.

Common problems and what causes them

"Unsupported state or unable to authenticate data" / InvalidTag / BadPaddingException
In CBC this almost always means the key or IV is wrong - the padding check fails because the decrypted bytes are garbage. It can also mean the ciphertext was truncated or is not a multiple of 16 bytes.
Key length is wrong
AES keys must be exactly 16, 24 or 32 bytes. A 32-character hex string is 16 bytes, not 32 - decode hex before use. Passing a password directly as a key is also wrong: derive a key with PBKDF2, scrypt or Argon2 first.
Losing the IV
The IV is not secret but it is required to decrypt. Store or transmit it with the ciphertext - the usual convention is to prepend its 16 bytes. Without it the ciphertext is unrecoverable.
Using CBC without authenticating the ciphertext
Without a MAC, an attacker can flip bits in the ciphertext and change the decrypted plaintext in predictable ways, and padding-oracle attacks can recover plaintext outright. Add an HMAC over IV+ciphertext and verify it before decrypting, or switch to AES-GCM.
Base64 vs hex vs raw bytes
The same ciphertext looks completely different in each encoding, and Base64 has URL-safe and standard variants that differ in two characters. Confirm which encoding each side uses before concluding the crypto is broken.
Using "AES" as the algorithm name in Java or .NET
Cipher.getInstance("AES") means AES/ECB/PKCS5Padding. ECB encrypts identical plaintext blocks to identical ciphertext blocks, which leaks structure badly. Always name the mode explicitly.

FAQ

What do I need to keep after encrypting with AES-CBC?
The ciphertext, the 16-byte IV, and a MAC over the two if you are doing this properly. None of those are secret, and all of them are required to decrypt. The conventional layout is to prepend the IV.
Do I need a new IV for every message?
Yes. The IV must be unpredictable for each message - a fixed or counter IV enables chosen-plaintext attacks that reveal whether two messages share a prefix. Generate 16 fresh random bytes per message.
Should I use AES-GCM or AES-CBC?
AES-GCM for anything new: it authenticates as well as encrypts, needs no padding, and is faster on modern hardware. Choose CBC only when an existing format or API requires it, and then always add an HMAC over the IV and ciphertext and verify it before decrypting.
Is the IV secret?
No. The IV is public and is normally stored or sent alongside the ciphertext. What matters is that it is unpredictable rather than a fixed constant.
Is AES-128 strong enough, or should I use AES-256?
AES-128 has no practical attack and is fine for essentially all use. AES-256 gives a larger margin against future advances at a small performance cost. Both are dramatically safer than getting the mode or nonce handling wrong, which is where real failures come from.
Can I use a password as the key?
Not directly. Keys must be exactly 16, 24 or 32 random bytes. Run the password through a key derivation function - PBKDF2, scrypt or Argon2, with a random salt and a high iteration or cost parameter - and use its output as the key.
Why does the same plaintext give different ciphertext each time?
Because a fresh random IV is used per operation, and that is exactly what you want. Identical ciphertext for identical plaintext would tell an attacker which messages repeat.
Is my key or plaintext sent to a server?
No. All AES operations here run in your browser via the Web Crypto API. Nothing is uploaded or logged, so you can safely test with a real value - check your network tab, or use the page offline.

Related reading