The Three Decisions That Decide Whether Your AES Encryption Is Real Encryption
Most “AES encryption” tutorials get the algorithm right and the security wrong. AES-256 itself is unbreakable in any practical sense — what breaks encryption in production is the three decisions around it: the mode of operation, how the key is derived from a passphrase, and the encoding of the output bundle. Each one is a place where a “looks encrypted” payload becomes a “decryptable by anyone who reads your source code” payload. A purpose-built AES encryption tool makes all three decisions for you, in the right order, and exports them as a single portable bundle that round-trips correctly on the other side.

Why “just use AES” is not a security plan
The Advanced Encryption Standard itself is a block cipher — it scrambles one 16-byte block at a time. Once you have chosen AES-256 as the primitive, you still have to decide four things that the cipher deliberately leaves open:
- How blocks chain together when the input is longer than 16 bytes.
- Whether the ciphertext changes when you encrypt the same plaintext twice.
- Whether anyone tampering with the ciphertext gets detected.
- How the secret key is actually generated from whatever the user typed in.
A bad answer to any of those gives you a payload that looks like random noise and is, in fact, fully recoverable. The three decisions that decide whether your AES encryption is real encryption are mode of operation, key derivation, and output encoding. They show up in every stack — Node, Python, Java, Go, the browser — and getting them wrong in any one of them collapses the whole scheme.
Decision one: pick a mode that authenticates, not just encrypts
The historical default is CBC — Cipher Block Chaining. It hides patterns between blocks, but it does not detect tampering. An attacker who flips a single bit in a CBC ciphertext flips a predictable bit in the plaintext after decryption, and your application has no way to notice. CBC also needs a separate integrity layer (HMAC, or a hand-rolled MAC) and a non-deterministic IV, both of which have to be wired correctly. In practice CBC is a footgun.
The modern default is GCM — Galois/Counter Mode. AES-GCM produces both ciphertext and a 128-bit authentication tag in a single pass, using the same key for both. If a single bit of the ciphertext or the IV changes, the tag fails to verify on decryption and the operation is rejected. That is what “authenticated encryption” means: confidentiality and integrity in one construction, with no extra MAC key to forget. AES-256-GCM is what TLS 1.3 uses for everything, and it is what the AES encryption tool defaults to. The result type the tool returns is a JSON bundle, and that bundle carries the IV and the tag alongside the ciphertext so they survive the round trip.

Decision two: derive the key from the passphrase, do not use the passphrase directly
If your “secret key” is literally the eight-character string the user typed in, you have 96 bits of entropy in the best case and somewhere between 20 and 40 bits in the realistic case. AES-256 will happily encrypt with that weak key — it does not know the key came from a dictionary — but an attacker who can guess the passphrase recovers the plaintext. The whole point of using AES-256 instead of AES-128 is to make the keyspace large; throwing the keyspace away by feeding it a dictionary word is the most common AES deployment mistake.
The fix is a key derivation function: PBKDF2, scrypt, argon2. They take a low-entropy passphrase and a salt and stretch it into a full 256-bit key by running many rounds of hashing. The salt stops rainbow-table attacks; the round count slows brute force. PBKDF2-SHA-256 with at least 100,000 iterations is the minimum serious answer; argon2id is the modern best choice. The tool exposes this as the Secret Key input — minimum eight characters, but the longer and more random the passphrase, the closer you get to the real 256-bit security AES-256 was designed for. There is no “secure mode” button that papers over a bad passphrase, so this decision is on you.

Decision three: encode the output so the round trip survives
After AES-GCM finishes, you have a buffer of bytes: 12 bytes of IV, N bytes of ciphertext, 16 bytes of tag. That buffer is the encrypted payload, and it is binary — it cannot be pasted into a JSON string, a URL, an email subject line, or a database column without further encoding. Two encodings dominate:
- Base64 — text-safe, URL-safe variant exists, expands the data by ~33 percent, and is what every web API defaults to.
- Hex — human-readable, doubles the data size, easier to eyeball in logs, more brittle under copy-paste because most browsers will silently strip leading zeros.
For round-trip safety across copy-paste, the cleaner pattern is to wrap the whole bundle — IV, ciphertext, tag, and the salt used for key derivation — as a JSON object, then base64-encode each binary field inside it. That is exactly the bundle the tool returns: you can paste it into a Slack message, copy it out six hours later, and decrypt it back to the original plaintext as long as you still have the passphrase. Skipping this layer — encrypting into raw bytes and hoping the calling code handles the framing — is the third decision that decides whether your AES encryption is real encryption.

What a correct AES round trip actually looks like
Putting the three decisions together, a correct round trip in any language looks like this: take a passphrase, generate a random salt, run PBKDF2-SHA-256 (≥100k iterations) to derive a 256-bit key, generate a fresh random 12-byte IV, run AES-256-GCM with that key and that IV over the plaintext, concatenate the salt + IV + ciphertext + tag, base64-encode the whole thing into a JSON bundle, hand it back. Decryption is the reverse: parse the bundle, base64-decode, split into salt + IV + ciphertext + tag, re-derive the key with the same passphrase and salt, run AES-GCM-decrypt, fail loudly if the tag does not verify. Every step is deterministic except the IV and salt, which is what makes the same plaintext encrypt to a different ciphertext every time. That non-determinism is also what makes the encryption actually secure — a deterministic encryption scheme leaks equality of plaintexts for free.
You can see the full workflow in the cryptography sample hub at Elysia Tools cryptography samples, where the same bundle format is implemented in TypeScript, Python, Go, Rust, and several mobile runtimes — useful when you need to encrypt on a server and decrypt on a phone, or vice versa, and want to verify both ends agree on the framing before you trust it with real data.
When AES-256-GCM is not the right answer
GCM fails closed: if the tag verification fails, decryption returns an error, not a partial plaintext. That is the correct behavior for at-rest encryption and authenticated messages, but it is the wrong behavior when you need a stream cipher (ChaCha20-Poly1305 is the modern equivalent), when you need deterministic encryption for search (AES-SIV), or when you need to encrypt something larger than memory (AES-XTS for whole-disk). For password storage, neither AES nor any block cipher is the right primitive — argon2id or scrypt with the right parameters is. The AES encryption tool is for “I have a secret value, I have a passphrase, I want to keep the value confidential and detect tampering” — which is most of what people actually mean when they say “encrypt this.”
For everything outside that envelope — file-level encryption of multi-gigabyte blobs, in-place encrypted search, password hashing — reach for the matching specialized primitive instead of twisting AES to fit.
Putting it together
The reason a small purpose-built AES encryption tool is worth using is not that it knows the algorithm — every language’s standard library knows AES — but that it picks the three surrounding decisions correctly, every time, and exports a self-describing bundle that round-trips through copy-paste without you having to remember whether the IV came before or after the salt, or whether the tag is appended or prepended. Open the tool, paste a passphrase of at least twelve random characters, encrypt, copy the bundle, decrypt on the other side, done. If you only remember one thing, remember this: AES is the easy part. The mode, the key derivation, and the output framing are the part that takes an actual decision, and they are what decide whether your “encrypted” payload is something only the passphrase holder can read, or something anyone who reads your code can read. Explore more tools at elysiatools.com.
