Encrypting sensitive data can hide its contents while still leaving an important question unanswered: has the encrypted data been changed? If an application decrypts modified ciphertext without a reliable integrity check, it may consume attacker-influenced plaintext even though the attacker never learned the encryption key.
That distinction matters anywhere decrypted data affects a security decision, a payment amount, a permission, a destination, or another meaningful application state. Confidentiality answers who can read the data. Integrity answers whether the protected data is the same data that an authorized key holder produced.
A practical default for new application-level encryption is authenticated encryption: a construction that protects confidentiality and also produces authentication information that must verify before the plaintext is accepted. This article explains that mental model, what authenticated encryption does and does not guarantee, and how to use it without quietly discarding the integrity property you intended to gain.
Encryption and integrity solve different problems
Start with a simple record:
account = 42
role = viewerSuppose an application encrypts this record before storing it in a location that another system can read or modify. Encryption should stop someone without the key from learning the plaintext directly.
But secrecy is not the same as tamper detection.
Some encryption schemes provide confidentiality without authenticating the ciphertext. Depending on the scheme, modifying encrypted bytes may produce modified plaintext, corrupted plaintext, or a decryption error. The application cannot treat “the plaintext looks plausible” as proof that nobody changed the ciphertext.
The defensive question is therefore not only:
Can an unauthorized party read this value?It is also:
Can the application detect unauthorized modification before it uses the value?Authenticated encryption is designed to answer both questions under its cryptographic assumptions.
Use a ciphertext and tag as one protected object
An authenticated-encryption operation commonly takes a key, a nonce, plaintext, and optionally some associated data. It produces ciphertext plus an authentication tag.
A simplified model is:
key + nonce + plaintext + associated data
|
v
authenticated encryption
|
v
ciphertext + tagThe tag is authentication information computed as part of the construction. During decryption, the recipient supplies the required inputs and verifies the tag. If verification succeeds, the operation returns plaintext. If verification fails, the ciphertext, tag, associated data, key, or other required input is not consistent with what the authenticated-encryption operation expects.
The important application rule is simple:
verify successfully -> release plaintext to the application
verification fails -> reject the protected valueDo not build application logic that treats unverified plaintext as usable data.
This is one reason to prefer a well-designed authenticated-encryption API rather than manually combining low-level encryption and authentication primitives. The API can make “decrypt and verify” one operation instead of asking application code to coordinate several cryptographic steps correctly.
Understand the threat model
Authenticated encryption is useful when an attacker may be able to observe or modify ciphertext but does not possess the encryption key. Under the assumptions of the selected algorithm and correct key and nonce handling, successful authentication gives the application strong evidence that the protected ciphertext and authenticated context were produced by someone holding the relevant key and were not modified without detection.
That reduces the risk of accepting attacker-modified encrypted data.
It does not solve every trust problem.
If an attacker steals the key, the attacker may be able to create ciphertext and tags that verify. If several services share one symmetric key, a valid tag does not identify which of those services created a message. If an authorized application encrypts malicious or incorrect data, authenticated encryption preserves that data faithfully; it does not decide whether the data was semantically valid.
It also does not automatically prevent replay or rollback. An old ciphertext with a valid tag can still be cryptographically authentic. If freshness or ordering matters, the protocol or data model needs an authenticated version, sequence, generation, timestamp policy, or another suitable freshness mechanism.
Think of authenticated encryption as protecting the integrity and confidentiality of a cryptographic object, not as a complete authorization or lifecycle system.
Authenticate context that must not be substituted
Authenticated encryption with associated data, often shortened to AEAD, can authenticate information without encrypting that information.
Suppose a system stores an encrypted preference value for a user. The database row already contains the user identifier in clear text because the application needs it for lookup:
user_id: 42
ciphertext: ...
tag: ...If the ciphertext is valid only for user 42, the user identifier can be included as associated data:
associated data = "user:42"The identifier remains visible, but changing the authenticated context causes verification to fail when the application supplies the expected context during decryption.
This is useful for binding ciphertext to facts that are not secret but are security-relevant, such as a record identifier, tenant identifier, message type, schema version, or protocol purpose.
The mental model is:
encrypted plaintext -> confidential + authenticated
associated data -> visible + authenticatedAssociated data is not a place to hide secrets. Its value is that it can make substitution across contexts detectable.
For example, if two database fields contain the same kind of encrypted bytes but have different meanings, authenticating a stable field identifier can reduce the risk that a valid ciphertext copied from one field is accepted in the other. The exact context to authenticate depends on the application’s data model. Include values whose unauthorized substitution would change how the plaintext is interpreted or where it is allowed to be used.
Treat authentication failure as a hard boundary
A dangerous integration pattern is to regard authentication failure as a recoverable formatting problem:
try authenticated decryption
if verification fails:
try legacy decryptionIf arbitrary untrusted ciphertext can reach that fallback, the application may have recreated an unauthenticated path precisely when authentication says the input should not be trusted.
Migration from legacy encryption may genuinely require more than one format. Make the format choice from trusted, explicit metadata rather than from “authentication failed.” For example, a versioned envelope can state which supported format a record uses, and the application can apply the exact verification rules for that version.
version 2 record -> AEAD verification required
version 1 record -> controlled legacy migration path
unknown version -> rejectThe migration path should be temporary, observable, and bounded. Once old records are migrated, remove the weaker path instead of leaving it available indefinitely.
Authentication failure should also produce a controlled application error. Do not return partially decrypted data, continue with default values that increase privilege, or expose low-level cryptographic diagnostics to an untrusted client. Log enough non-sensitive context for operators to investigate repeated failures without logging keys, plaintext, or secret material.
Keep nonce handling inside the design
Authenticated encryption does not remove algorithm-specific requirements. Many AEAD schemes require a nonce that is unique for each encryption operation under a given key. A nonce does not normally need to be secret, but violating the selected scheme’s nonce requirement can damage confidentiality, integrity, or both.
The repository has a separate article on nonce uniqueness because the operational problem deserves its own treatment. The key point here is that choosing an AEAD algorithm is not enough. The application must also satisfy that algorithm’s requirements for keys, nonces, tag handling, message sizes, and any other defined limits.
Prefer a maintained cryptographic library with a high-level AEAD interface. Follow the library and algorithm documentation for nonce generation and storage rather than inventing a nonce format from intuition.
Store or transmit the nonce and tag in the format the application needs to perform authenticated decryption. They are commonly carried alongside the ciphertext; secrecy of those values is generally not the property that protects the plaintext. The key is the secret that must remain protected.
Do not invent an encrypt-then-authenticate protocol casually
It is possible to build secure systems from separate encryption and message-authentication primitives, but the composition rules matter. Key separation, ordering, encoding, algorithm selection, and verification behavior all become part of the security design.
For application developers who simply need to protect data, a standard AEAD construction exposed by a reputable library usually gives a smaller and more reviewable interface:
seal(key, nonce, plaintext, context) -> protected value
open(key, nonce, protected value, context) -> plaintext or failureThe exact function names vary by library. The important property is that successful decryption and authentication are coupled.
Avoid designing a custom format where encryption happens in one component, a checksum is added in another, and the application later decides whether the checksum is “good enough.” A normal checksum detects accidental corruption; it is not a substitute for keyed cryptographic authentication against an active attacker.
Similarly, encoding ciphertext with Base64 or another text encoding changes representation, not security. Encoding can make binary data easier to transport, but it does not add confidentiality or integrity.
Decide what the application should authenticate
A useful design exercise is to write down the complete meaning of one protected value before choosing the encryption call.
Suppose an application encrypts an API integration configuration. Ask:
What plaintext must remain confidential?
What visible context must not be substituted?
Which key is allowed to create this protected object?
How will nonce requirements be satisfied?
What should happen if verification fails?
Does the application need freshness or anti-replay state too?Imagine the protected value belongs to tenant acme, configuration record 817, and purpose outbound-webhook. The secret configuration can be encrypted while stable context is authenticated as associated data.
The application should reconstruct the expected associated data from trusted state when decrypting. If it simply accepts both ciphertext and its claimed context from the same untrusted source, an attacker may be able to move the entire valid pair together. Cryptography can detect modification of authenticated bytes; it cannot know that the application supplied the wrong expected context.
This is a trust-boundary decision. Bind the ciphertext to context the application independently knows or intentionally controls.
Verify the control with negative tests
A successful round trip proves only that encryption and decryption agree on the happy path. Security tests should also exercise rejection.
For a test record, confirm that the application rejects the protected value when you independently change:
- one byte of ciphertext;
- the authentication tag;
- authenticated associated data;
- the nonce, when the format requires the original nonce for decryption; or
- a version or purpose value that is part of the authenticated context.
Also verify the application behavior after rejection. No plaintext should reach business logic, no privileged default should replace the failed value, and no legacy path should silently accept it.
These tests are especially useful around serialization boundaries. A cryptographic design can be correct while the surrounding application accidentally omits a field from associated data, verifies one representation but uses another, or catches a verification error and continues.
Know the limits of the control
Authenticated encryption is a strong building block, but its guarantees are conditional.
It relies on secret-key protection. It relies on correct nonce handling where the algorithm requires nonce uniqueness. It relies on the application authenticating the context that actually matters. It relies on rejecting verification failures before plaintext is used. It also relies on using an algorithm and implementation appropriate for the system’s requirements.
Operationally, key rotation and data migration still need a plan. Version the protected format so the application can identify which key generation and algorithm are expected without guessing from decryption success. Keep old keys only as long as they are needed to read data that has not yet been migrated, subject to the system’s recovery and retention requirements.
For low-risk data where accidental corruption is the only concern and there is no attacker in the threat model, a non-cryptographic checksum may be sufficient. Once untrusted parties can modify data that the application later treats as authoritative, cryptographic integrity becomes a different requirement.
For sensitive application data that also needs confidentiality, authenticated encryption lets one protected object carry both properties. Additional controls such as authorization, replay protection, key isolation, secure backups, and monitoring remain necessary when the threat model calls for them.
Conclusion
Encryption should not force an application to choose between keeping data secret and knowing whether that data was modified. For new application-level encryption, use a standard authenticated-encryption construction when both confidentiality and integrity matter.
The practical rule is to treat verification as part of decryption, not as an optional check afterward. Authenticate security-relevant context, satisfy the algorithm’s nonce and key requirements, reject verification failures before using plaintext, and add separate controls for threats such as key compromise, replay, rollback, and authorization.
That design gives the application a clear boundary: protected data becomes usable only after its cryptographic integrity has been successfully verified.