FiloSottile/age · critical
failed to decrypt and authenticate payload chunk, file may…
Error message
failed to decrypt and authenticate payload chunk, file may be corrupted or tampered with
What it means
The ChaCha20Poly1305 AEAD Open call failed on a payload chunk, meaning authentication (MAC) or decryption failed. Since the header MAC already passed, this means the encrypted payload was modified, corrupted, or encrypted with a different file key. The reader treats any single bad chunk as a fatal error for the whole file.
Solutions
- Confirm you are using the correct identity/private key that the file was encrypted for.
- Re-download or re-copy the file and verify its checksum against the sender.
- Re-encrypt from the original plaintext if the source is corrupted beyond repair.
- Check that no transport step (FTP ascii mode, editor save) mangled the binary ciphertext.
Example fix
// before
r, _ := age.Decrypt(input, wrongIdentity) // payload chunks fail auth
// after
r, err := age.Decrypt(input, correctIdentity)
if err != nil { return fmt.Errorf("decrypt: %w", err) } Defensive patterns
Strategy: try-catch
Try / catch
if _, err := io.Copy(out, r); err != nil {
if strings.Contains(err.Error(), "failed to decrypt and authenticate payload chunk") {
return errors.New("wrong key or corrupted/tampered file")
}
return err
} Prevention
- Verify you hold the identity the file was encrypted for before long processing.
- Use binary-safe transfer (no text-mode FTP, no editor round-trips) for .age files.
- Distribute checksums alongside ciphertext and verify before decrypting.
When it happens
Trigger: stream.Reader.readChunk calls r.a.Open and it returns a non-nil error — wrong identity/key (mismatched file key), bit rot, truncated bit flips, or tampered ciphertext bytes within any chunk.
Common situations: Decrypting with the wrong identity (file encrypted for someone else); corrupted downloads or damaged storage; malicious modification of the .age file; copying the file in text mode that altered bytes.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/a9d82a98f8141e38.
Report an issue: GitHub.
Appendix: source
Thrown at internal/stream/stream.go:150
return false, errors.New("last chunk is empty, try age v1.0.0, and please consider reporting this")
}
in = in[:n]
last = true
setLastChunkFlag(&r.nonce)
case err != nil:
return false, err
}
outBuf := make([]byte, 0, ChunkSize)
out, err := r.a.Open(outBuf, r.nonce[:], in, nil)
if err != nil && !last {
// Check if this was a full-length final chunk.
last = true
setLastChunkFlag(&r.nonce)
out, err = r.a.Open(outBuf, r.nonce[:], in, nil)
}
if err != nil {
return false, errors.New("failed to decrypt and authenticate payload chunk, file may be corrupted or tampered with")
}
incNonce(&r.nonce)
r.unread = r.buf[:copy(r.buf[:], out)]
return last, nil
}
func incNonce(nonce *[chacha20poly1305.NonceSize]byte) {
for i := len(nonce) - 2; i >= 0; i-- {
nonce[i]++
if nonce[i] != 0 {
return
}
}
// The counter is 88 bits, this is unreachable.
panic("stream: chunk counter wrapped around")
}
View on GitHub (pinned to b74dce4cdb)