DevGizmo
Back to Blog
cryptography·

AES Encryption Explained: AES-256-CBC, Keys, IVs, and Padding

AES (Advanced Encryption Standard) is the world's most widely used symmetric encryption algorithm. Learn how AES-256 works, what CBC mode means, why initialization vectors matter, and how to use AES correctly in your applications.

aesencryptioncryptographysymmetric-encryption

What Is AES?

AES (Advanced Encryption Standard) is a symmetric block cipher standardised by NIST in 2001. "Symmetric" means the same key is used to encrypt and decrypt. AES is used in TLS/HTTPS, disk encryption (BitLocker, FileVault), VPNs, Wi-Fi (WPA2/WPA3), and virtually every secure communication protocol in use today.

AES operates on fixed 128-bit (16-byte) blocks of data. It supports three key sizes: 128-bit, 192-bit, and 256-bit. AES-256 is the strongest and is the recommended choice for new implementations.

How AES Works

AES processes each 16-byte block through multiple rounds of four operations:

  1. SubBytes — each byte is replaced via a substitution table (S-box)
  2. ShiftRows — rows of the block matrix are cyclically shifted
  3. MixColumns — columns are mixed using field multiplication
  4. AddRoundKey — the block is XOR'd with the current round key

AES-128 uses 10 rounds, AES-192 uses 12, and AES-256 uses 14. The mathematical underpinning (Galois Field arithmetic) makes AES exceptionally resistant to known attacks.

Block Cipher Modes: CBC, GCM, and CTR

A block cipher alone only encrypts a single 16-byte block. Modes of operation define how to chain multiple blocks together.

CBC (Cipher Block Chaining)

Each plaintext block is XOR'd with the previous ciphertext block before encryption:

C[i] = Encrypt(P[i] XOR C[i-1])
C[0] = Encrypt(P[0] XOR IV)

The Initialization Vector (IV) is a random 16-byte value used for the first block. It ensures that encrypting the same plaintext twice produces different ciphertext. The IV does not need to be secret but must be unique for each encryption operation.

CBC requires PKCS#7 padding to bring the last block to 16 bytes if the plaintext is not a multiple of 16.

GCM (Galois/Counter Mode)

GCM is a stream cipher mode that also provides authentication (it's an Authenticated Encryption with Associated Data mode — AEAD). GCM produces a tag alongside the ciphertext; decryption fails if the tag does not match, detecting tampering.

Prefer GCM over CBC for new applications. GCM is used in TLS 1.3 and most modern protocols.

CTR (Counter Mode)

CTR Mode turns a block cipher into a stream cipher by encrypting a counter value and XOR'ing the output with the plaintext. Like GCM (which is CTR under the hood), it does not require padding.

The Initialization Vector (IV)

The IV must be:

  • Unique for each encryption operation using the same key (critical for CBC and CTR)
  • Unpredictable (random) for CBC mode; counter-based is fine for CTR/GCM
  • Not secret — the IV is typically prepended to the ciphertext and used during decryption

Never reuse an IV with the same key. IV reuse in CBC mode allows an attacker to detect when two messages share a plaintext prefix. IV reuse with the same key in CTR/GCM completely breaks the encryption.

// Generate a secure random IV (Node.js)
const iv = crypto.randomBytes(16);

Key Derivation from Passwords

Do not use a raw password string as an AES key. Passwords have much lower entropy than a proper 256-bit key, and their length rarely matches the required 32 bytes.

Use a key derivation function (KDF) to derive a proper key from a password:

const { scryptSync, createCipheriv } = require("crypto");

const password = "user-entered-password";
const salt = crypto.randomBytes(16); // store alongside the ciphertext
const key = scryptSync(password, salt, 32); // 32 bytes = 256 bits

const iv = crypto.randomBytes(16);
const cipher = createCipheriv("aes-256-cbc", key, iv);

Common KDFs: scrypt, PBKDF2, Argon2. All include a "salt" to prevent rainbow table attacks.

AES in the Web Crypto API

async function encryptAES(plaintext, key) {
  const iv = crypto.getRandomValues(new Uint8Array(16));
  const encoded = new TextEncoder().encode(plaintext);
  const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoded);
  return { iv, ciphertext };
}

The Web Crypto API uses GCM by default (AES-GCM), which is the recommended mode. It does not expose AES-CBC directly in the encrypt method in some browser contexts.

Common Mistakes to Avoid

  • ECB mode — encrypts each block independently, leaking patterns (the famous "ECB penguin" image). Never use ECB mode.
  • Hardcoded keys — keys must be generated securely and stored separately (environment variables, key management services).
  • Reusing IVs — always generate a fresh random IV per encryption.
  • Not authenticating — without GCM or a separate MAC, an attacker can silently modify ciphertext (padding oracle attacks target CBC without authentication).

Try it yourself

Put these concepts into practice with the free online tool on DevGizmo.