About AES Encrypt & Decrypt
AES is a symmetric block cipher standardised in 2001 and still the default for encrypting data at rest and in transit. The same key encrypts and decrypts, and it is fast - modern CPUs carry dedicated AES instructions, so throughput is measured in gigabytes per second rather than megabytes.
The decision that actually matters is not AES but the mode of operation, because AES alone only transforms a single 128-bit block. The mode determines how a message of arbitrary length is handled, and it is where security is won or lost. Choosing AES-256 over AES-128 is nearly irrelevant next to choosing GCM over CBC, or handling a nonce correctly.
For anything new, use AES-GCM. It is an authenticated mode: one operation produces the ciphertext and a 128-bit authentication tag, and decryption verifies that tag before returning anything, so tampering is detected rather than silently decrypted into plausible-looking garbage. It needs no padding and no separate MAC. Its one hard rule is that a nonce must never be reused with the same key - doing so leaks the XOR of the plaintexts and lets an attacker forge tags.
AES-CBC is what you find in existing systems: older APIs, encrypted database columns, payment integrations, file formats that predate authenticated encryption. It provides confidentiality only. Without a MAC an attacker can modify ciphertext and change the decrypted plaintext, and padding-oracle attacks can recover plaintext outright. If you must use CBC, apply an HMAC over the IV and ciphertext and verify it before decrypting - encrypt-then-MAC, in that order.
ECB deserves an explicit warning, because libraries still offer it and it is frequently the default when no mode is named. It encrypts each block independently, so identical plaintext blocks produce identical ciphertext blocks and the structure of the data shows through the encryption. In Java, Cipher.getInstance("AES") means AES/ECB/PKCS5Padding. Never use it.
Whichever mode you choose, a key is not a password. AES keys must be exactly 16, 24 or 32 random bytes; a password has to go through a key derivation function - PBKDF2, scrypt or Argon2 with a random salt - first. Everything here runs in your browser via the Web Crypto API, so nothing is uploaded.
How to use the AES Encrypt & Decrypt
- Decide the mode before anything else: GCM for new work, CBC only when an existing format requires it, never ECB.
- Provide a key of exactly 16, 24 or 32 bytes, decoded from hex or Base64 rather than used as printable text. Derive it from a password with PBKDF2 or Argon2 if that is what you have.
- Let a fresh random IV or nonce be generated for every operation, and never reuse one with the same key.
- Store or transmit the IV/nonce and, for GCM, the authentication tag alongside the ciphertext - you cannot decrypt without them.
Examples
-
Plaintext
Sensitive config value
AES Encrypt & Decrypt in code
The same operation this tool performs, in the languages you are most likely to need it.
// NEW WORK: AES-GCM. Authenticated, no padding, no separate MAC.
const key = await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]
);
const iv = crypto.getRandomValues(new Uint8Array(12)); // fresh EVERY time
const ct = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, data);
// Store { iv, ct } - the 16-byte tag is already appended to ct.
// LEGACY ONLY: AES-CBC, and only with an HMAC over IV+ciphertext,
// verified BEFORE decrypting. Never CBC on its own.
// NEVER: ECB. This is what plain "AES" means in Java and .NET.
// Identical plaintext blocks give identical ciphertext blocks, so
// the shape of the data survives encryption.
// A password is NOT a key. AES needs exactly 16/24/32 random bytes.
async function keyFromPassword(password, salt) {
const base = await crypto.subtle.importKey(
"raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"]
);
return crypto.subtle.deriveKey(
{ name: "PBKDF2", salt, iterations: 600_000, hash: "SHA-256" },
base,
{ name: "AES-GCM", length: 256 },
false, ["encrypt", "decrypt"]
);
}
// The salt is random per password and stored with the ciphertext.
const salt = crypto.getRandomValues(new Uint8Array(16));
// 600,000 PBKDF2-SHA256 iterations is the current OWASP figure.
// Argon2id is a better choice wherever you can use it.
# AES-GCM
# ciphertext variable length
# nonce 12 bytes - not secret, must be UNIQUE per key
# tag 16 bytes - often appended to the ciphertext
# [AAD] optional additional authenticated data
# Conventional layout: nonce || ciphertext || tag
# AES-CBC
# ciphertext multiple of 16 bytes
# IV 16 bytes - not secret, must be UNPREDICTABLE
# MAC 32 bytes - HMAC-SHA256 over IV || ciphertext
# Conventional layout: IV || ciphertext || MAC
# Key sizes - note hex is twice the byte count
# AES-128 = 16 bytes = 32 hex characters
# AES-192 = 24 bytes = 48 hex characters
# AES-256 = 32 bytes = 64 hex characters
openssl rand -hex 32 # a correct AES-256 key
When you need this
- Deciding which AES mode a new feature should use.
- Working out what a system has to store alongside a ciphertext.
- Checking whether an existing integration uses an authenticated mode.
- Confirming a key is the right length for the AES variant you intend.
- Producing a test vector for either mode during an integration.
Common problems and what causes them
- Using "AES" without naming a mode
- Cipher.getInstance("AES") in Java means AES/ECB/PKCS5Padding, and several other libraries also default to ECB. ECB encrypts identical blocks identically, so patterns in the plaintext are visible in the ciphertext. Always name the mode explicitly.
- Choosing AES-256 and thinking the job is done
- Key size is the least consequential decision here. AES-128 has no practical attack, and no key size protects you from ECB, a reused GCM nonce, or CBC without a MAC. Get the mode and nonce handling right first.
- Using a password directly as a key
- Keys must be exactly 16, 24 or 32 random bytes. Passing a password - or a 32-character hex string, which is 16 bytes - either fails a length check or silently gives a weaker key than intended. Derive with PBKDF2, scrypt or Argon2 and a random salt.
- Reusing a nonce or IV
- In GCM this is catastrophic: it exposes the XOR of the plaintexts and lets an attacker recover the authentication subkey and forge tags. In CBC, a predictable IV enables chosen-plaintext attacks. Generate fresh random bytes per operation, never once at startup.
- CBC without authentication
- CBC gives confidentiality and no integrity. An attacker can flip ciphertext bits to change plaintext predictably, and a padding oracle can recover plaintext outright. Add an HMAC over IV and ciphertext, verified before decrypting - or move to GCM.
- Losing the IV or the tag
- Neither is secret and both are required. A ciphertext stored without its nonce is unrecoverable; a GCM ciphertext without its tag cannot be verified. Pick a layout - nonce || ciphertext || tag is conventional - and keep to it.
FAQ
- Which AES mode should I use?
- AES-GCM for anything new: it authenticates as well as encrypts, needs no padding and no separate MAC, and is hardware-accelerated. CBC only when an existing format or API requires it, and then always with an HMAC over the IV and ciphertext. Never ECB.
- Is AES-256 meaningfully safer than AES-128?
- Marginally, and it is not where your risk lies. AES-128 has no practical attack. The mode and nonce handling matter far more - a reused GCM nonce or unauthenticated CBC breaks AES-256 exactly as completely as AES-128.
- Why is ECB mode dangerous?
- It encrypts each 128-bit block independently, so identical plaintext blocks produce identical ciphertext blocks and the structure of the data survives encryption - the well-known encrypted-penguin image is ECB. It remains the default when a library is asked for plain "AES".
- Can I use a password as an AES key?
- Not directly. The key must be exactly 16, 24 or 32 random bytes. Run the password through PBKDF2 with 600,000 or more SHA-256 iterations, or scrypt or Argon2id, with a random salt, and use the derived bytes as the key.
- What do I need to store besides the ciphertext?
- For GCM: the 12-byte nonce and the 16-byte authentication tag. For CBC: the 16-byte IV and, done properly, a MAC over the IV and ciphertext. None of these are secret, and all are required to decrypt or verify.
- Is my data sent to a server?
- No. Every AES operation here runs in your browser through the Web Crypto API - nothing is uploaded or logged, and the page works offline once loaded.
- Is this production-grade?
- Fine for local experiments. Production apps should use vetted libraries, KMS, and key rotation - not clipboard passphrases.
- GCM vs CBC?
- Prefer GCM for new work - it provides authentication. CBC is included for interoperability with older systems.
Related reading
- AES-GCM vs AES-CBC: modes compared
- AES-GCM encrypt the recommended mode
- AES-CBC encrypt for legacy formats
- Random hex generator generate a key