FiloSottile/age · error
failed to decrypt and authenticate chunk at offset %d: %w
Error message
failed to decrypt and authenticate chunk at offset %d: %w
What it means
DecryptReaderAt.ReadAt read a chunk successfully, but ChaCha20Poly1305 authentication failed when opening it. Because each chunk carries its own AEAD tag, this means the bytes at that chunk offset are not the authentic ciphertext for this key and chunk index. In age's threat model this indicates corruption or tampering, and the read is aborted with any bytes already copied returned as n.
Source
Thrown at internal/stream/stream.go:443
chunkSize := min(encSize-chunkOff, encChunkSize)
cached := r.cache.Load()
var plaintext []byte
if cached != nil && cached.off == chunkOff {
plaintext = cached.data
cacheUpdate = nil
} else {
if err := readFullAt(r.src, chunk[:chunkSize], chunkOff); err != nil {
return n, fmt.Errorf("failed to read chunk at offset %d: %w", chunkOff, err)
}
nonce := nonceForChunk(chunkIndex)
if chunkIndex == r.chunks-1 {
setLastChunkFlag(nonce)
}
var err error
plaintext, err = r.a.Open(chunk[:0], nonce[:], chunk[:chunkSize], nil)
if err != nil {
return n, fmt.Errorf("failed to decrypt and authenticate chunk at offset %d: %w", chunkOff, err)
}
cacheUpdate = &cachedChunk{off: chunkOff, data: plaintext}
}
plainChunkOff := int(off - chunkIndex*ChunkSize)
copySize := min(len(plaintext)-plainChunkOff, len(p))
copy(p, plaintext[plainChunkOff:plainChunkOff+copySize])
p = p[copySize:]
off += int64(copySize)
n += copySize
}
if cacheUpdate != nil {
r.cache.Store(cacheUpdate)
}
if off == r.size {
return n, io.EOF
}
return n, nilView on GitHub (pinned to b74dce4cdb)
Solutions
- Confirm the key matches the recipient this file was encrypted for.
- Take a snapshot/immutable view of the ciphertext while reading; do not modify or re-encrypt the file concurrently.
- Verify file integrity (checksum) and restore from a known-good copy if corrupted.
- Ensure the size used at construction still matches the current source; a changed file invalidates chunk offsets.
Defensive patterns
Strategy: try-catch
Try / catch
n, err := dr.ReadAt(p, off)
if err != nil && !errors.Is(err, io.EOF) {
if strings.Contains(err.Error(), "failed to decrypt and authenticate chunk") {
return fmt.Errorf("ciphertext chunk corrupted or wrong key: %w", err)
}
return err
} Prevention
- Make the ciphertext read-only while a DecryptReaderAt is open.
- Verify the key/identity matches the file's recipient.
- Keep checksums to detect bit rot and restore from backups.
- Only the final chunk is pre-validated at construction; mid-file corruption surfaces here.
When it happens
Trigger: ReadAt(p, off) where aead.Open fails for the chunk at chunkOff: wrong key; ciphertext bytes for that chunk modified; the src content changed after NewDecryptReaderAt validated the final chunk (validation only authenticated the last chunk, not all chunks).
Common situations: A file being rewritten in place after opening the reader (e.g. rsync or atomic replace of the target); bit rot on storage; decrypting with the wrong identity; in-place edits by tools unaware of the format.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- failed to decrypt and authenticate final chunk: %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/66ae849fea94aae8.
Report an issue: GitHub.