AES Encryption for Developers When You Need It and How It Works
If you’re storing sensitive data — user PII, API keys, financial records — AES encryption is the tool you reach for. It’s the industry standard for symmetric encryption at rest: fast, battle-tested, and supported natively in every major language.
But AES done wrong is worse than useless. ECB mode leaks patterns. Reusing an IV with GCM breaks its security guarantee entirely. And storing your key next to your ciphertext defeats the whole point.
Here’s what you actually need to use AES correctly in production.
What Is AES?
AES (Advanced Encryption Standard) is a symmetric block cipher. Symmetric means the same key encrypts and decrypts. Block cipher means it operates on fixed 128-bit chunks of data.
It replaced DES in 2001, and today it’s everywhere: TLS, disk encryption, password managers, database field encryption. Unlike asymmetric encryption (RSA, ECDH), AES is fast enough to encrypt large amounts of data in real time. The tradeoff: both parties need the same key, so key distribution becomes your problem.
AES-128 vs AES-256: Which Key Size?
AES comes in three key sizes: 128, 192, and 256 bits. In practice you’ll choose between 128 and 256.
| AES-128 | AES-256 | |
|---|---|---|
| Key length | 128 bits | 256 bits |
| Rounds | 10 | 14 |
| Performance | Faster | ~40% slower |
| Security | Strong — no known practical attack | Stronger — future-proof against quantum |
| Verdict | Fine for most applications | Use for high-sensitivity data or long retention |
AES-128 is not broken. No practical attack exists. But if you’re encrypting data that needs to stay secure for 20+ years — or your threat model includes future quantum computers — AES-256 adds meaningful margin. For most applications encrypting database fields today, AES-128 is sufficient. When in doubt, use AES-256; the performance hit is negligible for typical workloads.
Mode of Operation: Why GCM, Not ECB
AES alone only encrypts a single 128-bit block. For real data you need a mode of operation that handles multiple blocks and sequences them correctly. This choice matters enormously.
| Mode | Authenticated | IV Required | Verdict |
|---|---|---|---|
| ECB | No | No | Never use — identical plaintext produces identical ciphertext, leaks patterns |
| CBC | No | Yes | Outdated — no authentication, vulnerable to padding oracle attacks |
| GCM | Yes (AEAD) | Yes (nonce) | Use this — encrypts and authenticates in one pass |
Use GCM. GCM (Galois/Counter Mode) is an AEAD cipher — Authenticated Encryption with Associated Data. It does two things at once:
- Encrypts your data so it’s unreadable without the key
- Produces an authentication tag so any tampering is immediately detectable
Without authentication, an attacker can flip bits in your ciphertext and you’ll decrypt garbage without knowing it. With GCM, decryption fails loudly if the ciphertext was modified — before any plaintext is returned.
The IV/Nonce: Random, Never Reused
GCM requires an initialization vector (IV), also called a nonce — “number used once.” The IV doesn’t need to be secret, but it must be:
- Random — generated with a cryptographically secure RNG
- Unique — never reused with the same key
Reusing an IV with the same key in GCM is catastrophic. An attacker who sees two ciphertexts encrypted with the same key and IV can XOR them together and recover both plaintexts. This is not theoretical — it has happened in production systems.
Use a 96-bit (12-byte) random IV per encryption operation. Since you need it for decryption, store it alongside the ciphertext — IV prepended to the ciphertext is the conventional format.
Working Code: AES-256-GCM
Here are production-ready snippets. Both store the IV and auth tag with the ciphertext so decryption is self-contained.
Node.js
const crypto = require('crypto');
const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32; // 256 bits
const IV_LENGTH = 12; // 96 bits — recommended for GCM
const TAG_LENGTH = 16; // 128-bit auth tag
function encrypt(plaintext, key) {
const iv = crypto.randomBytes(IV_LENGTH);
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
const encrypted = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final(),
]);
const tag = cipher.getAuthTag();
// Layout: [iv (12)] + [tag (16)] + [ciphertext]
return Buffer.concat([iv, tag, encrypted]).toString('base64');
}
function decrypt(ciphertextB64, key) {
const data = Buffer.from(ciphertextB64, 'base64');
const iv = data.subarray(0, IV_LENGTH);
const tag = data.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
const encrypted = data.subarray(IV_LENGTH + TAG_LENGTH);
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
}
// Usage
const key = crypto.randomBytes(KEY_LENGTH); // store this securely
const ciphertext = encrypt('sensitive data here', key);
const plaintext = decrypt(ciphertext, key);
Python
import os, base64
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
IV_LENGTH = 12 # 96 bits
def encrypt(plaintext: str, key: bytes) -> str:
iv = os.urandom(IV_LENGTH)
aesgcm = AESGCM(key)
# encrypt() appends the 16-byte auth tag automatically
ciphertext = aesgcm.encrypt(iv, plaintext.encode(), None)
return base64.b64encode(iv + ciphertext).decode()
def decrypt(ciphertext_b64: str, key: bytes) -> str:
data = base64.b64decode(ciphertext_b64)
iv = data[:IV_LENGTH]
ciphertext = data[IV_LENGTH:]
aesgcm = AESGCM(key)
return aesgcm.decrypt(iv, ciphertext, None).decode()
# Usage
key = AESGCM.generate_key(bit_length=256) # store this securely
ciphertext = encrypt('sensitive data here', key)
plaintext = decrypt(ciphertext, key)
Install the dependency: pip install cryptography
You can test AES encrypt and decrypt interactively — without any setup — using the IO Tools AES Encryption/Decryption tool.
Key Management: The Hard Part
Your encryption is only as strong as your key management. The most common mistakes:
- Key in the same database as the encrypted data — if an attacker gets your DB dump, they get both.
- Key committed to source control — even in a gitignored
.env, rotation becomes painful and history persists. - Key appearing in logs — application logs get shipped to aggregators. Keys should never surface there.
Where to actually store encryption keys:
- AWS KMS / Google Cloud KMS / Azure Key Vault — managed key storage with audit trails and automatic rotation
- HashiCorp Vault — self-hosted, good for multi-cloud environments
- Environment variables — acceptable for low-sensitivity workloads with limited exposure surface
For database field encryption, envelope encryption is the standard pattern: generate a data encryption key (DEK) per record or column, then encrypt the DEK with a master key stored in a KMS. Your database contains only encrypted DEKs and ciphertext — the master key never touches the database.
When to Use AES — and When Not To
AES is the right tool for:
- Encrypting fields in a database — SSNs, card numbers, health records
- Encrypting files at rest before cloud storage
- Secret sharing between services that share a pre-established key
AES is not the right tool when:
- You’re transmitting data over a network — use TLS. Don’t roll your own transport layer.
- You need asymmetric encryption — if sender and receiver can’t share a key in advance, use RSA or ECDH for key exchange.
- You’re storing passwords — use bcrypt, scrypt, or Argon2. Encrypted passwords can be decrypted; properly hashed ones can’t.
- A managed solution fits — AWS Secrets Manager, Vault, and similar handle rotation, access control, and audit logs out of the box. Prefer that over manual AES if it covers your use case.
The mistake most developers make is treating AES as a complete security solution. It’s a primitive — a building block. Pair it with authenticated mode (GCM), unique per-operation IVs, and proper key management, and it’s extremely effective. Skip any of those pieces and you’ve built something that looks secure but isn’t.
Install Our Extensions
Add IO tools to your favorite browser for instant access and faster searching
恵 Scoreboard Has Arrived!
Scoreboard is a fun way to keep track of your games, all data is stored in your browser. More features are coming soon!
Must-Try Tools
View All New Arrivals
View AllUpdate: Our latest tool was added on Apr 22, 2026
