What is the difference between Cipher.update and Cipher.doFinal, and when do you use each?
answer
- update = partial, may return 0 bytes (buffering)
- doFinal = finish: padding or GCM tag + reset
- One-shot small data: just doFinal
- Stream large data: update… then doFinal
- GCM: update bytes are unverified until doFinal
basics
~20 supdate feeds part of the data into the cipher and may return some processed bytes, but it doesn't finish the operation. doFinal processes the last chunk, applies padding (or the GCM tag), and completes. For small data you just call doFinal once.
solid answer
~40 sBoth move data through an initialized Cipher, but doFinal completes the operation while update does not. update(bytes) processes a chunk and returns whatever output is ready so far (possibly an empty array, because the cipher may buffer until it has a full block); the cipher stays open for more input. doFinal processes any remaining input plus buffered bytes, applies/removes padding for block modes or appends/verifies the tag for GCM, and resets the cipher for reuse. Use update/doFinal for streaming large data (encrypting a file in chunks); for small in-memory data call doFinal(plaintext) once. Critical for AEAD: bytes from update are unverified — the GCM tag is only checked at doFinal — so don't trust or emit them until doFinal succeeds. After doFinal you can re-init for the next message.
go deeper
Knows doFinal completes the encryption and that small data needs only one doFinal call.
Explains buffering (update may return fewer/zero bytes), the streaming loop, and that doFinal applies padding/tag and resets.
Articulates the GCM unverified-update hazard, uses streaming correctly for large data, and manages cipher state/reuse safely.
Sets patterns for large-payload crypto (streaming, memory bounds, AEAD safety) and reviews CipherInputStream/GCM misuse across services.
## Why two methods AES is a **block cipher** processing 16 bytes at a time, and you often have data larger than memory (a big file) or arriving incrementally (a network stream). The `Cipher` API therefore supports **incremental** processing through two calls: - **`update(byte[] input)`** — push a chunk of data in. It returns the output that is **ready so far**. This may be **fewer bytes than you put in, or even zero**, because the cipher **buffers** partial blocks: it can only emit a complete 16-byte ciphertext block once it has a full plaintext block. The operation is **not finished** — you can keep calling `update`. - **`doFinal()`** (or `doFinal(byte[] lastChunk)`) — **finish** the operation. It processes any buffered/remaining bytes, then: - for **CBC/ECB**: adds padding (encrypt) or removes and validates it (decrypt), - for **GCM**: appends the authentication tag (encrypt) or recomputes and **verifies** it (decrypt), and finally **resets** the cipher so it can be re-`init`'d for another message. ## Typical usage **Small data (one shot):** ```java byte[] ct = cipher.doFinal(plaintext); // update is unnecessary ``` **Streaming a file:** ```java while ((n = in.read(buf)) != -1) { byte[] out = cipher.update(buf, 0, n); if (out != null) sink.write(out); // may be empty/null mid-stream } sink.write(cipher.doFinal()); // flush + finalize ``` In practice you'd use `CipherInputStream`/`CipherOutputStream`, which wrap this loop for you. ## The AEAD trap With **GCM**, the authentication tag is only verified inside `doFinal`. Therefore any bytes returned by `update` during **decryption** are **unauthenticated** — they could be attacker-tampered. You must **not act on, display, or persist** them until `doFinal` returns successfully. If it throws `AEADBadTagException`, discard everything. (This is a known footgun with `CipherInputStream` + GCM: it can yield unverified bytes before the tag check.) ## State and reset A `Cipher` is **stateful** across `update` calls and **not thread-safe**. After `doFinal`, the engine is reset; to encrypt another message, call `init` again — and for GCM use a **fresh nonce**, never the same one.
- Why might update return an empty array even though you passed in data?AES processes whole 16-byte blocks, so the cipher buffers a partial block until it has a complete one. Until enough bytes accumulate (or padding is finalized), update has no complete block to emit and returns empty.
- What does CipherInputStream/CipherOutputStream give you?They wrap the update/doFinal loop in the standard stream API, encrypting/decrypting transparently as you read/write. Convenient, but with GCM they can hand back unverified bytes before doFinal's tag check, so use them cautiously for AEAD.
saying these in an interview costs you the question
- Assuming update returns the same number of bytes you passed in
- Forgetting to call doFinal, so padding/tag is never applied and data is incomplete
- Trusting GCM update output before doFinal verifies the tag
- Reusing a Cipher concurrently from multiple threads