
September 11, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Plain encryption hides data, but it does not prove the ciphertext is intact. An attacker can flip bits, replay old tokens, or swap blocks without the receiver noticing. Authenticated Encryption (AEAD) Explained in practical terms means one cryptographic operation that both encrypts plaintext and produces an authentication tag you must verify before decryption. If you build REST APIs with sensitive payloads, client portals, or payment flows, AEAD is the default pattern you should reach for instead of rolling your own MAC-plus-cipher stack.
What is Authenticated Encryption (AEAD) and why does it matter?
AEAD is a symmetric cipher mode that outputs ciphertext plus a fixed-length authentication tag. Decryption verifies the tag first. A mismatch means you discard the message and never release plaintext. That single gate closes entire classes of attacks that classic CBC or ECB modes leave open.
Historically, teams stitched together AES-CBC plus HMAC-SHA256 in an encrypt-then-MAC arrangement. That works when implemented perfectly. In practice, ordering mistakes, key reuse, and timing leaks show up in code reviews and incident reports. AEAD modes bake the contract into the algorithm: one key (or derived subkeys), one nonce, one API surface.
On legal-tech portals and document-sharing systems I have shipped, AEAD protects session cookies, API tokens, and uploaded file metadata at the application layer. The same idea applies whether you store secrets in Redis or pass encrypted IDs in URLs. Integrity is not optional when a forged token could expose someone else's divorce filing or notary appointment.
The term AEAD appears in RFC 5116, which defines authenticated-encryption modes for IPsec and general use. Modern libraries expose AEAD through a small set of well-tested primitives. Your job is to call them correctly, not to invent a new mode.
Confidentiality plus integrity in one API
Confidentiality stops passive eavesdroppers from reading content. Integrity stops active attackers from modifying ciphertext without detection. AEAD delivers both. Associated Authenticated Data (AAD) lets you authenticate context—such as a user ID or API version—without encrypting it. The tag covers AAD plus ciphertext, so tampering with either fails verification.
How AEAD differs from older patterns
Compare AEAD to three legacy approaches:
- Encrypt-only (AES-CBC, AES-ECB): No integrity. Padding-oracle and bit-flipping attacks are real.
- MAC-only (HMAC over plaintext): No confidentiality. Fine for signed webhooks, not for secret payloads.
- Encrypt-then-MAC: Secure when done right, but two keys, two code paths, and more review surface.
For greenfield work in 2026, default to AEAD. Reach for hybrid encryption when you need asymmetric key exchange, then use AEAD for the bulk data key.
Which AEAD algorithms should developers use in 2026?
Two algorithms dominate production stacks today: AES-GCM and ChaCha20-Poly1305. Both are AEAD. Both require a unique nonce per encryption under the same key. Pick based on hardware and library support, not brand preference.
| Algorithm | Key size | Nonce size | Best on | Notes |
|---|---|---|---|---|
| AES-256-GCM | 256 bits | 96 bits (12 bytes) | CPUs with AES-NI | Standard in TLS 1.3, AWS KMS, many databases |
| ChaCha20-Poly1305 | 256 bits | 96 bits (12 bytes) | Mobile, older x86, embedded | Constant-time friendly; default in libsodium |
| AES-128-GCM | 128 bits | 96 bits | Same as AES-GCM | Acceptable; prefer 256 for long-lived app keys |
AES-GCM security depends on never reusing a (key, nonce) pair. Duplicate nonces can leak authentication keys and plaintext XOR relationships. NIST SP 800-38D documents GCM constraints that every implementer should read once.
PHP 8.3 and later ship sodium by default. That extension uses ChaCha20-Poly1305 for sodium_crypto_secretbox. OpenSSL exposes AES-GCM through openssl_encrypt with mode aes-256-gcm. Laravel's built-in Crypt facade uses AES-256-CBC with HMAC—a sound encrypt-then-MAC design, but not AEAD. For new custom crypto inside Laravel 12 or 13 apps, prefer sodium or explicit GCM calls over ad-hoc OpenSSL modes.
How does AES-GCM work under the hood?
GCM combines CTR-mode encryption with Galois-field multiplication for authentication. CTR turns AES into a stream cipher: you XOR a keystream with plaintext. The auth tag is a polynomial hash over ciphertext and AAD. Verification recomputes the tag and compares in constant time inside the library.
You do not implement GCM math yourself. You supply key, nonce, plaintext, and optional AAD. The library returns ciphertext concatenated with tag (or separate outputs, depending on API). Document your wire format so the next developer knows byte layout.
Nonce rules that actually matter
A 96-bit nonce is standard for GCM. Common strategies:
- Random 96-bit nonce: Safe if you use a CSPRNG and keep message volume far below birthday bounds.
- Counter nonce: Store a monotonic counter in the database or Redis. Predictable but easy to audit.
- Random key per message (envelope): Generate a fresh data key, encrypt with a master key, embed key ID in payload. See envelope encryption patterns for KMS-style designs.
Never derive the nonce from a timestamp alone. Clock skew and replay collide. Never use a fixed nonce "because it is easier." That breaks GCM catastrophically.
Wire format you should standardise
A practical on-disk or on-the-wire blob often looks like this:
version (1 byte) | key_id (4 bytes) | nonce (12 bytes) | ciphertext | tag (16 bytes) Store the version byte so you can rotate algorithms later. Include key_id when multiple keys exist during rotation. Encode the whole structure with Base64 only for transport, not as a security layer. Our Base64 encoder and decoder helps debug payloads during integration tests.
How do you implement AEAD safely in PHP and Laravel?
Below are copy-paste starting points for PHP 8.3+ with libsodium and OpenSSL AES-GCM. Run them on PHP 8.5 or any supported 8.3+ build in production. Test on staging before you touch live client data.
ChaCha20-Poly1305 with libsodium
<?php
declare(strict_types=1);
function aead_encrypt(string $plaintext, string $key, string $aad = ''): string
{
if (strlen($key) !== SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
throw new InvalidArgumentException('Key must be 32 bytes.');
}
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
$ciphertext = sodium_crypto_aead_chacha20poly1305_ietf_encrypt(
$plaintext,
$aad,
$nonce,
$key
);
return $nonce . $ciphertext;
}
function aead_decrypt(string $blob, string $key, string $aad = ''): string
{
$nonceLen = SODIUM_CRYPTO_SECRETBOX_NONCEBYTES;
$nonce = substr($blob, 0, $nonceLen);
$ciphertext = substr($blob, $nonceLen);
$plaintext = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(
$ciphertext,
$aad,
$nonce,
$key
);
if ($plaintext === false) {
throw new RuntimeException('AEAD verification failed.');
}
return $plaintext;
} Bind AAD to tenant ID or document type on multi-tenant portals. A ciphertext moved from one tenant context to another must fail decrypt. I use that pattern on production Laravel applications that handle per-client legal documents.
AES-256-GCM with OpenSSL
<?php
declare(strict_types=1);
function aes_gcm_encrypt(string $plaintext, string $key, string $aad = ''): string
{
$nonce = random_bytes(12);
$tag = '';
$ciphertext = openssl_encrypt(
$plaintext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$nonce,
$tag,
$aad,
16
);
if ($ciphertext === false) {
throw new RuntimeException('Encryption failed.');
}
return $nonce . $tag . $ciphertext;
}
function aes_gcm_decrypt(string $blob, string $key, string $aad = ''): string
{
$nonce = substr($blob, 0, 12);
$tag = substr($blob, 12, 16);
$ciphertext = substr($blob, 28);
$plaintext = openssl_decrypt(
$ciphertext,
'aes-256-gcm',
$key,
OPENSSL_RAW_DATA,
$nonce,
$tag,
$aad
);
if ($plaintext === false) {
throw new RuntimeException('AEAD verification failed.');
}
return $plaintext;
} Wrap these helpers in a service class. Inject keys from environment variables or a secrets manager. Never hard-code keys in Git. For Ansible-managed servers, compare with encrypting secrets in Ansible Vault at the infrastructure layer while AEAD protects data inside the app.
Laravel integration checklist
When wiring AEAD into Laravel 12 or 13:
- Register an
EncryptionServicesingleton with keys loaded fromconfig/services.php. - Encrypt PII columns in model mutators or use a custom cast class.
- Log verification failures without logging ciphertext or keys.
- Add PHPUnit tests that flip one tag byte and assert decrypt throws.
- Plan key rotation: decrypt with old key, re-encrypt with new key in a queued job.
Pair this with database encryption at rest and in transit. TLS covers the wire. Disk encryption covers stolen drives. AEAD covers logical access when someone dumps a table.
What are the most common AEAD mistakes in production?
Most AEAD failures are operational, not mathematical. The algorithm is fine; the integration is not.
Nonce reuse and key management errors
Reusing a nonce under the same AES-GCM key leaks the auth subkey and XOR relationships between messages. Rotating keys without updating key_id in stored blobs causes decrypt failures at 2 a.m. Storing the master key next to ciphertext in the same database defeats the purpose. Use environment secrets, Ansible Vault for deployment secrets, or a KMS.
Skipping verification or leaking failure details
Never catch AEAD failures and continue with empty strings. Never branch on "which byte of the tag failed." Return a generic 401 or 400 to the client. Log internally with a correlation ID. Detailed crypto errors help attackers refine forgeries.
Confusing encoding with encryption
Base64 is not encryption. JSON wrapping is not integrity. Hashing is not reversible encryption. Use the password generator for user passwords, then password_hash with Argon2id. Use AEAD for reversible secrets like API tokens you must read back.
On portals like Mijar Law Associates and Notary Nepal, a forged document reference must fail closed. AEAD on opaque identifiers makes guessing and bit-flipping impractical when combined with proper authorisation checks.
Performance myths
AEAD adds overhead, but it is small next to database and network latency. AES-GCM on AES-NI hardware often encrypts faster than your ORM loads relations. Profile before you disable integrity. If you process bulk exports, stream chunks and encrypt in jobs on a queue worker.
For API designs that expose encrypted IDs publicly, also read API rate limiting and abuse prevention. Crypto slows attackers; rate limits stop brute force at the edge.
How does AEAD fit with asymmetric and envelope encryption?
AEAD is symmetric. You still need a safe way to share or wrap keys. Typical stack:
- Generate a random 256-bit data key per file or per session.
- Encrypt the payload with AEAD under that data key.
- Wrap the data key with RSA-OAEP or another KEM using the recipient public key.
- Ship wrapped key plus AEAD blob together.
That is envelope encryption. AWS KMS and similar services automate wrapping. Read symmetric vs asymmetric encryption for the full split of responsibilities. AEAD handles bulk data; asymmetric crypto handles key establishment.
At rest, PostgreSQL 18 and MySQL 9.7 offer transparent encryption options, but application-layer AEAD still helps when you must encrypt single columns or pass tokens to third parties who should not read raw SQL backups.
Key Takeaways
- Use AEAD (AES-GCM or ChaCha20-Poly1305) instead of encrypt-only modes or hand-rolled MAC composition.
- Generate a unique 96-bit nonce for every encryption under the same key; document your blob format and rotation plan.
- Verify the authentication tag before parsing JSON or loading ORM models; fail closed with generic errors.
- Bind associated data to tenant, user, or resource context so ciphertext cannot be cut-and-pasted across scopes.
- Combine TLS in transit, disk encryption at rest, and AEAD at the field layer for defense in depth.
- Test tamper cases in CI: one flipped tag byte must always throw, never return partial plaintext.
People Also Ask
Is AES-GCM the same as AEAD?
AES-GCM is one AEAD mode, not the whole category. AEAD is the property: encryption plus authentication in one operation. GCM is the most common AES-based AEAD mode in TLS, cloud KMS APIs, and OpenSSL. ChaCha20-Poly1305 is equally AEAD and often preferred on systems without hardware AES acceleration.
What happens if an AEAD tag fails verification?
The decrypt function must return failure and release no plaintext. Your application should treat that like any other auth failure: stop processing, return a safe error, and log internally. Do not retry with stripped tags or fall back to unauthenticated parsing.
Can I use Laravel Crypt instead of AEAD?
Laravel's default Crypt facade uses AES-256-CBC with a valid HMAC—encrypt-then-MAC, not AEAD, but sound for framework-managed payloads like signed cookies. For custom token formats, cross-service compatibility, or explicit GCM requirements, call sodium or OpenSSL GCM directly inside a dedicated service class.
Do I still need HTTPS if I use AEAD?
Yes. AEAD protects stored fields and application-level blobs. TLS protects data on the network, including keys and nonces in transit. They solve different layers. Use both, plus access control and audit logging, on any system handling payments or personal data.
Build encryption you can maintain after launch
Authenticated Encryption (AEAD) Explained boils down to a simple rule: one well-vetted algorithm, unique nonces, verified tags, and clear key rotation. Skip custom cipher modes. Pick AES-GCM or ChaCha20-Poly1305, write tests that tamper with tags, and document the byte layout your future self will need at 2 a.m.
If you are adding field-level encryption to a Laravel portal, hardening API tokens, or reviewing crypto before a compliance audit, treat AEAD as the default—not an optional extra. Need help designing or reviewing encryption for a production app? Contact us or explore custom software development and ongoing support and maintenance. For related reading, see the blog encryption series and secure legal-tech implementations in our portfolio.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

