FiloSottile/age · error
invalid encrypted payload size: %d
Error message
invalid encrypted payload size: %d
What it means
EncryptedChunkCount validates the size of an encrypted STREAM payload. This error means the caller passed a size that cannot possibly be a valid encrypted payload: either a negative number, or a size so large the chunk count arithmetic would overflow. The library refuses to guess a chunk count from malformed sizes because the chunk structure determines the plaintext layout.
Source
Thrown at internal/stream/stream.go:25
import (
"bytes"
"crypto/cipher"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"sync/atomic"
"golang.org/x/crypto/chacha20poly1305"
)
const ChunkSize = 64 * 1024
func EncryptedChunkCount(encryptedSize int64) (int64, error) {
if encryptedSize < 0 || encryptedSize > math.MaxInt64-encChunkSize+1 {
return 0, fmt.Errorf("invalid encrypted payload size: %d", encryptedSize)
}
chunks := (encryptedSize + encChunkSize - 1) / encChunkSize
plaintextSize := encryptedSize - chunks*chacha20poly1305.Overhead
expChunks := (plaintextSize + ChunkSize - 1) / ChunkSize
// Empty plaintext, the only case that allows (and requires) an empty chunk.
if plaintextSize == 0 {
expChunks = 1
}
if expChunks != chunks {
return 0, fmt.Errorf("invalid encrypted payload size: %d", encryptedSize)
}
return chunks, nil
}
func PlaintextSize(encryptedSize int64) (int64, error) {
chunks, err := EncryptedChunkCount(encryptedSize)View on GitHub (pinned to b74dce4cdb)
Solutions
- Verify the size you pass is the ciphertext (encrypted) size, not the plaintext size; ciphertext = plaintext + 16 bytes per 64 KiB chunk.
- Check the value is non-negative and comes from a reliable source such as os.File.Stat().Size() after the write completed.
- If the file may be truncated or incomplete, wait for the writer to finish or re-stat the file before computing sizes.
- If a sentinel negative value is in play, handle 'unknown size' explicitly before calling the API.
Example fix
// before
n, err := stream.EncryptedChunkCount(-1) // sentinel for unknown
// after
if size < 0 {
return 0, errors.New("encrypted size unknown; stat the file first")
}
n, err := stream.EncryptedChunkCount(size) Defensive patterns
Strategy: validation
Validate before calling
const encChunkSize = 64*1024 + 16
func validEncryptedSize(size int64) bool {
return size >= 0 && size <= math.MaxInt64-encChunkSize+1
}
// call EncryptedChunkCount only if validEncryptedSize(size) Try / catch
chunks, err := stream.EncryptedChunkCount(size)
if err != nil {
return fmt.Errorf("cannot determine chunk layout for size %d: %w", size, err)
} Prevention
- Always pass ciphertext size (Stat().Size()), never plaintext size or sentinels like -1.
- Reject negative and 'unknown' sizes before calling into the stream package.
- Don't hand-compute chunk counts; let the library derive them.
When it happens
Trigger: Calling stream.EncryptedChunkCount(encryptedSize) with encryptedSize < 0 or with encryptedSize > math.MaxInt64 - encChunkSize + 1. Also surfaces via PlaintextSize and NewDecryptReaderAt, which call EncryptedChunkCount internally.
Common situations: Passing a plaintext file size instead of the ciphertext size; passing an os.Stat size from a truncated or still-being-written file; integer misuse where -1 is used as a sentinel for 'unknown size'.
Related errors
- non-EOF error reading after end of encrypted file: %w
- failed to read final chunk: %w
- failed to decrypt and authenticate final chunk: %w
- offset out of range [0:%d]: %d
- failed to read chunk at offset %d: %w
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/4ef4c5fc65f77f49.
Report an issue: GitHub.