FiloSottile/age · error
failed to decrypt and authenticate final chunk: %w
Error message
failed to decrypt and authenticate final chunk: %w
What it means
NewDecryptReaderAt authenticates the final chunk with ChaCha20Poly1305 using the last-chunk nonce. This error means the final chunk's ciphertext failed AEAD verification: the data is not the authentic final chunk for this key. Unlike error [73], the bytes were read successfully but decryption/authentication failed, which age treats as corruption or tampering (or a wrong key).
Source
Thrown at internal/stream/stream.go:402
}
// Check that size is valid by decrypting the final chunk.
chunks, err := EncryptedChunkCount(size)
if err != nil {
return nil, err
}
finalChunkIndex := chunks - 1
finalChunkOff := finalChunkIndex * encChunkSize
finalChunkSize := size - finalChunkOff
finalChunk := make([]byte, finalChunkSize)
if err := readFullAt(src, finalChunk, finalChunkOff); err != nil {
return nil, fmt.Errorf("failed to read final chunk: %w", err)
}
nonce := nonceForChunk(finalChunkIndex)
setLastChunkFlag(nonce)
plaintext, err := aead.Open(finalChunk[:0], nonce[:], finalChunk, nil)
if err != nil {
return nil, fmt.Errorf("failed to decrypt and authenticate final chunk: %w", err)
}
cache := &cachedChunk{off: finalChunkOff, data: plaintext}
plaintextSize := size - chunks*chacha20poly1305.Overhead
r := &DecryptReaderAt{a: aead, src: src, size: plaintextSize, chunks: chunks}
r.cache.Store(cache)
return r, nil
}
func (r *DecryptReaderAt) ReadAt(p []byte, off int64) (n int, err error) {
if off < 0 || off > r.size {
return 0, fmt.Errorf("offset out of range [0:%d]: %d", r.size, off)
}
if len(p) == 0 {
return 0, nil
}
var cacheUpdate *cachedChunk
chunk := make([]byte, encChunkSize)View on GitHub (pinned to b74dce4cdb)
Solutions
- Confirm you are using the correct key for this recipient/file; wrong keys surface as authentication failure.
- Verify the ciphertext is byte-for-byte intact (checksum against the source of truth); re-download or restore if corrupted.
- Ensure the size argument matches this exact ciphertext version; a mismatched size shifts the final-chunk offset and guarantees failure.
- If files come from a mixed toolchain, re-encrypt with the same age STREAM implementation to rule out incompatibilities.
Example fix
// before key := deriveKey(wrongPassphrase) r, err := stream.NewDecryptReaderAt(key, f, size) // after key := deriveKey(correctPassphrase) // verify key provenance r, err := stream.NewDecryptReaderAt(key, f, size)
Defensive patterns
Strategy: try-catch
Try / catch
r, err := stream.NewDecryptReaderAt(key, src, size)
if err != nil {
if strings.Contains(err.Error(), "failed to decrypt and authenticate final chunk") {
return fmt.Errorf("wrong key or corrupted ciphertext: %w", err)
}
return err
} Prevention
- Verify the key/identity matches the recipient before decrypting.
- Keep ciphertext files immutable while in use (no in-place edits).
- Ship and verify checksums alongside ciphertext to detect transport corruption.
- Use one consistent encryption toolchain/version.
When it happens
Trigger: NewDecryptReaderAt(key, src, size) where aead.Open on the final chunk (nonce = chunk index with last-chunk flag) fails: wrong key, wrong size causing the wrong 'final chunk' window, or ciphertext modified in place.
Common situations: Decrypting with the wrong key/identity; the ciphertext file was edited, corrupted in transit, or re-padded; the file was produced by a different scheme or a different final-chunk convention; using a stale size with a rewritten file.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- failed to decrypt and authenticate chunk at offset %d: %w
- invalid encrypted payload size: %d
- non-EOF error reading after end of encrypted file: %w
- failed to read final chunk: %w
- offset out of range [0:%d]: %d
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/071aa8b52d40208a.
Report an issue: GitHub.