Understanding AES file encryption
A passphrase, a key derivation, and a cipher.
What "encrypt this file with a password" really involves, and why the choice between AES-GCM and AES-CBC is one of the few cryptography decisions worth thinking about.
Three steps, not one.
"Encrypt with a password" is shorthand for three operations: derive a strong key from the password (PBKDF2 / Argon2 / scrypt), choose a random IV/nonce, run the file bytes through an authenticated cipher (AES-GCM). Each step matters. Skip the key derivation and the password's entropy becomes the encryption strength — usually well below 128 bits. Reuse the IV and AES-GCM falls apart catastrophically.
AES-GCM is the modern default.
AES-GCM (Galois/Counter Mode) does two things at once: encrypts the data and authenticates it. The output is the ciphertext plus a small "authentication tag" (16 bytes) that a recipient must verify before decrypting. If the ciphertext was tampered with, the tag check fails and decryption refuses. AES-CBC encrypts but doesn't authenticate; you'd have to compose it with HMAC manually, and that composition is famously easy to get wrong. Use GCM.
The IV must never repeat.
GCM's strongest requirement: the IV (also called nonce) must be unique for every encryption performed with the same key. Reuse an IV once and you leak the XOR of the two plaintexts; reuse it twice and the key itself starts leaking through forgeable tags. Generate the IV from a CSPRNG (12 bytes of random) for every encryption. Store the IV with the ciphertext — it's not secret.
A worked file layout.
A reasonable on-disk format for a password-encrypted file: a header carrying the key-derivation parameters (algorithm, salt, iteration count), the random IV, then the AES-GCM ciphertext including its 16-byte tag. [magic 4 bytes][version 1][kdf_id 1][salt 16][iv 12][iterations 4][ciphertext...][tag 16] Decryption: read the header, re-derive the key from the user's password + stored salt + iteration count, decrypt the ciphertext, verify the tag.
Encrypt
key = KDF(password, salt) ; iv = random(12)
Derive once per file; never reuse iv.
ct, tag = AES-GCM-encrypt(key, iv, plaintext)
= ciphertext + auth tag
Decrypt
key = KDF(password, salt) ; iv from header
Recompute key from the same salt and iterations.
pt = AES-GCM-decrypt(key, iv, ct, tag)
= Original bytes or error
The key derivation function matters.
PBKDF2 is the safe default — widely supported, available in every crypto library, parameterised by iteration count. OWASP currently recommends 600,000+ iterations of PBKDF2-SHA256. Argon2 is the modern winner of the Password Hashing Competition — memory-hard and slower for attackers, but with more setup. scrypt is the older memory-hard option. The choice is "PBKDF2 with enough iterations" for compatibility, "Argon2id with sensible parameters" for new code.
Why this runs in the browser.
Browser crypto.subtle ships native AES-GCM and PBKDF2. The file never leaves your machine — important for actually-secret files and for everything you can't legally hand to a third party. The cost is performance: a 100MB file takes a few seconds to derive a key, encrypt, and emit. For larger files, stream chunks through; for any file, never serialise the whole plaintext to a string.