InTab

Blog

AES encryption explained: GCM vs CBC vs CTR, IVs and keys

By Uxair Ijaz · 2026-10-24 · 8 min read

AES (the Advanced Encryption Standard) protects most of the encrypted data in the world: HTTPS connections, Wi-Fi, disk encryption, password managers, messaging apps. The cipher itself has resisted more than twenty years of attacks. When AES encryption fails in practice, it is almost never AES that breaks. It is the choices around it: the mode, the IV, the key and whether anything checks that the data was not changed.

This guide explains those choices so you can read code and documentation with confidence: what a block cipher does, how GCM, CBC and CTR differ, why IVs must never repeat, how to turn a password into a key, and the mistakes that cause real breaches. You can follow along in the Encrypt / Decrypt tool, which uses your browser's built-in Web Crypto.

AES is a block cipher

AES on its own does one small thing: it takes a 16-byte block and a key and produces another 16-byte block that looks random. With the key you can reverse it; without the key you cannot.

Key size Rounds Notes
128-bit (16 bytes) 10 Secure for any practical purpose
192-bit (24 bytes) 12 Rarely used
256-bit (32 bytes) 14 Extra margin, including against future quantum attacks; often required by policy

AES-128 is not "weak", but AES-256 costs little extra and is the common default. The Encrypt / Decrypt tool offers both.

Real messages are longer than 16 bytes, so AES is always used in a mode of operation, a recipe for applying the block cipher to data of any length. The mode matters more than the key size.

The modes

ECB: never use it

ECB encrypts each 16-byte block on its own. Identical plaintext blocks give identical ciphertext blocks, so patterns survive encryption: the famous example is an image of the Linux penguin, still clearly visible after ECB encryption. ECB is the default in some old libraries, which is reason enough to always name your mode explicitly.

CBC: chained blocks

CBC combines each plaintext block with the previous ciphertext block (by XOR) before encrypting it. The first block is combined with an IV, a random 16-byte starting value, so encrypting the same message twice gives different results.

  • The IV must be random and unpredictable for every message; a counter or a fixed value is not enough.
  • Messages are padded to a multiple of 16 bytes (usually with PKCS#7 padding).
  • CBC does not detect tampering. Worse, servers that reveal whether padding was valid (through different errors or timings) are open to padding oracle attacks, which can decrypt data without the key.

CBC is safe only when combined with a separate MAC, applied as encrypt-then-MAC, and checked before decrypting. That is easy to get wrong.

CTR: a stream of keystream

CTR encrypts a counter (nonce plus block number) and XORs the result with the plaintext. It turns AES into a stream cipher: no padding, parallel processing, and the ciphertext is exactly as long as the plaintext.

  • The nonce/counter combination must never repeat under the same key. If it does, XORing the two ciphertexts cancels the keystream and reveals the XOR of the two plaintexts.
  • CTR does not detect tampering either, and it is malleable: flipping a bit in the ciphertext flips the same bit in the decrypted text. An attacker who knows a message says amount=100 can change it to amount=900 without knowing the key.

GCM: encryption plus authentication

GCM is CTR encryption plus a 16-byte authentication tag computed over the ciphertext. Decryption checks the tag first; if a single bit of the ciphertext, IV or tag has changed, it fails with an error instead of returning altered data. This combination is called AEAD, authenticated encryption with associated data, and it is what you want by default.

  • Use a 12-byte (96-bit) nonce, unique for every message under a given key. Random nonces are fine for up to about 2³² messages per key; beyond that, use a counter or rotate the key.
  • Nonce reuse in GCM is catastrophic: as with CTR it reveals the XOR of the plaintexts, and it also lets an attacker forge valid tags.
  • Associated data is optional extra context, such as a record id or header, that is authenticated but not encrypted. Decryption fails if it does not match, which binds the ciphertext to its context.
Mode Hides data Detects tampering IV / nonce Padding Verdict
ECB Partly, patterns leak No None Yes Never use
CBC Yes No 16 bytes, random Yes Only with a separate MAC
CTR Yes No 16-byte counter block, unique No Only with a separate MAC
GCM Yes Yes 12 bytes, unique No Default choice

Other good AEAD choices exist, such as ChaCha20-Poly1305, which is fast on devices without AES hardware. AES-GCM-SIV is a variant of GCM that limits the damage if a nonce is ever reused. Both follow the same rule: authenticated encryption, every time.

IVs and nonces

The IV (CBC) or nonce (CTR, GCM) is not secret. It is normally stored or sent right next to the ciphertext, often as its first bytes. The one rule is that it must be unique for each message under the same key, and for CBC it must also be unpredictable.

Generate it with a secure random source (crypto.getRandomValues in browsers) for every message. Never hard-code it, never derive it from the message, and never reset a counter when a server restarts.

Keys: random bytes or a password

AES needs a key of exactly 16 or 32 random bytes. You get one in one of two ways.

A random key

Generate 32 bytes from a secure random source and store them somewhere safe: a secrets manager, a key management service or an environment variable, never source code. Hex-encoded, a 256-bit key is 64 characters. This is the right approach for servers and services. In the tool, choose Raw key.

A password, through a key-derivation function

People cannot remember 32 random bytes, so when a person types a password you must turn it into a key with a slow key-derivation function such as PBKDF2, scrypt or Argon2id. A KDF does two things:

  1. It mixes in a random salt, stored with the ciphertext, so the same password gives a different key every time.
  2. It is deliberately slow, so guessing passwords is expensive.

The Encrypt / Decrypt tool in Password mode uses PBKDF2-HMAC-SHA-256 with 600,000 rounds by default (OWASP's recommendation) and a 16-byte random salt. Never use a password, or a plain SHA-256 of it, directly as a key. The password hashing guide explains why fast hashes fail.

What the encrypted output contains

To decrypt, the receiver needs everything except the key: the salt (if a password was used), the IV and the ciphertext with its tag. Implementations differ in how they pack these, which is the most common reason encrypted data "will not decrypt" between two tools. The tool uses this layout and then encodes the whole thing as Base64 or hex:

[ salt: 16 bytes, password mode only ][ IV: 12 bytes GCM, 16 bytes CBC/CTR ][ ciphertext ][ GCM tag: 16 bytes ]

When exchanging encrypted data with another system, agree on all of it: mode, key size, KDF and its settings, the layout of salt, IV and tag, and the text encoding (Base64, Base64URL or hex). For example, the openssl enc command derives keys differently unless you pass -pbkdf2, and does not support GCM at all.

AES in JavaScript

Browsers and Node.js include AES through the Web Crypto API. A minimal AES-GCM round trip with a random key:

const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']);
const iv = crypto.getRandomValues(new Uint8Array(12)); // new for every message

const data = new TextEncoder().encode('Meet at noon');
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, data);

const plain = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext); // throws if anything was changed
console.log(new TextDecoder().decode(plain));

Store or send iv with ciphertext; Web Crypto appends the 16-byte tag to the ciphertext for you.

Mistakes to avoid

  • ECB mode, or any library default you have not checked.
  • Unauthenticated modes (CBC, CTR) without a MAC. Prefer GCM.
  • Reusing an IV or nonce with the same key, including fixed IVs in configuration files.
  • Passwords used directly as keys, or keys derived with a single fast hash.
  • Keys in source code or committed configuration.
  • Ignoring decryption errors, or showing different errors for bad padding and bad data.
  • Encrypting passwords for storage. Passwords should be hashed. See Hashing vs encryption.
  • Inventing a protocol. For files and messages between people, established formats such as age, OpenPGP or libsodium's secretbox handle the details for you.

Try it

Open the Encrypt / Decrypt tool:

  1. Choose Encrypt, AES-GCM (recommended) and Password, type a message and a password, and encrypt.
  2. Encrypt the same message again and note the different output: a fresh salt and IV each time.
  3. Switch to Decrypt, paste the result and the password, and get your message back.
  4. Change one character of the encrypted text and decrypt again. GCM rejects it. Try the same with AES-CTR and see the difference.

Everything happens in your browser; nothing you type is uploaded.

Tools in this article

  • Encrypt / Decrypt: AES-GCM, CBC and CTR text encryption with a password (PBKDF2) or a raw key.
  • Password Generator: Generate strong random passwords, passphrases and PINs.
  • Base64: Encode and decode Base64 text and files (URL-safe supported).