ory/hydra · error

plaintext too large

Error message

plaintext too large

What it means

Encrypt computes the ciphertext size as plaintext length + nonce size + Poly1305 overhead and guards the addition against integer overflow before allocating. A plaintext larger than math.MaxInt minus these fixed overheads cannot be safely encrypted, so Encrypt rejects it up front.

Source

Thrown at aead/xchacha20.go:43

func NewXChaCha20Poly1305(d Dependencies) *XChaCha20Poly1305 {
	return &XChaCha20Poly1305{d}
}

func (x *XChaCha20Poly1305) Encrypt(ctx context.Context, plaintext, additionalData []byte) (string, error) {
	key, err := encryptionKey(ctx, x.d, chacha20poly1305.KeySize)
	if err != nil {
		return "", err
	}

	aead, err := chacha20poly1305.NewX(key)
	if err != nil {
		return "", errors.WithStack(err)
	}

	// Make sure the size calculation does not overflow.
	if len(plaintext) > math.MaxInt-aead.NonceSize()-aead.Overhead() {
		return "", errors.WithStack(fmt.Errorf("plaintext too large"))
	}

	nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(plaintext)+aead.Overhead())
	_, err = cryptorand.Read(nonce)
	if err != nil {
		return "", errors.WithStack(err)
	}

	ciphertext := aead.Seal(nonce, nonce, plaintext, additionalData)
	return base64.URLEncoding.EncodeToString(ciphertext), nil
}

func (x *XChaCha20Poly1305) Decrypt(ctx context.Context, ciphertext string, aad []byte) (plaintext []byte, err error) {
	msg, err := base64.URLEncoding.DecodeString(ciphertext)
	if err != nil {
		return nil, errors.WithStack(err)
	}

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Reduce the plaintext size; do not encrypt arbitrary-size payloads with AEAD — encrypt a content key instead and use streaming encryption for the payload.
  2. Split large data into chunks encrypted independently.
  3. If the input comes from user uploads, enforce a size limit before calling Encrypt.
  4. Fix code paths that read unbounded input into a single byte slice.

Example fix

// before
blob, _ := io.ReadAll(r.Body)
enc, err := x.Encrypt(ctx, blob)

// after
if r.ContentLength > maxPayload { http.Error(w, "too large", 413); return }
blob, _ := io.ReadAll(io.LimitReader(r.Body, maxPayload))
enc, err := x.Encrypt(ctx, blob)
Defensive patterns

Strategy: validation

Validate before calling

const maxPlaintext = 1 << 20 // choose a sane app-level limit
if len(plaintext) > maxPlaintext {
    return fmt.Errorf("payload too large to encrypt: %d bytes", len(plaintext))
}

Try / catch

enc, err := x.Encrypt(ctx, plaintext)
if err != nil && strings.Contains(err.Error(), "plaintext too large") {
    return nil, ErrPayloadTooLarge
}

Prevention

When it happens

Trigger: Calling Encrypt with a plaintext whose length exceeds math.MaxInt - chacha20poly1305.NonceSizeX - overhead (practically an enormous buffer, near the platform max int).

Common situations: Accidentally passing a streamed file read entirely into memory; a bug where an unbounded/uninitialized buffer is encrypted; 32-bit builds where MaxInt is small enough that large request bodies trip the check.

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/3946ec7acf183f13. Report an issue: GitHub.