juicedata/juicefs · error

malformed ciphertext: %d %d

Error message

malformed ciphertext: %d %d

What it means

Inside dataEncryptor.Decrypt, the 3-byte header encodes the encrypted data-key length and nonce length. If 3+keyLen+nonceLen is not strictly less than the total length, there is no room left for actual ciphertext, so the buffer is declared malformed.

Source

Thrown at pkg/object/encrypt.go:265

	buf[1] = byte(len(cipherkey) & 0xFF)
	buf[2] = byte(len(nonce))
	p := buf[3:]
	copy(p, cipherkey)
	p = p[len(cipherkey):]
	copy(p, nonce)
	p = p[len(nonce):]
	ciphertext := aead.Seal(p[:0], nonce, plaintext, nil)
	return buf[:headerSize+len(ciphertext)], nil
}

func (e *dataEncryptor) Decrypt(ciphertext []byte) ([]byte, error) {
	if len(ciphertext) < 3 {
		return nil, fmt.Errorf("received encrypted text length is less than 3, the object is corrupted")
	}
	keyLen := int(ciphertext[0])<<8 + int(ciphertext[1])
	nonceLen := int(ciphertext[2])
	if 3+keyLen+nonceLen >= len(ciphertext) {
		return nil, fmt.Errorf("malformed ciphertext: %d %d", keyLen, nonceLen)
	}
	ciphertext = ciphertext[3:]
	cipherkey := ciphertext[:keyLen]
	nonce := ciphertext[keyLen : keyLen+nonceLen]
	ciphertext = ciphertext[keyLen+nonceLen:]

	key, err := e.keyEncryptor.Decrypt(cipherkey)
	if err != nil {
		return nil, errors.New("decryt key: " + err.Error())
	}
	aead, err := e.aead(key)
	if err != nil {
		return nil, err
	}
	return aead.Open(ciphertext[:0], nonce, ciphertext, nil)
}

// MaxOverhead returns the maximum number of extra bytes that Encrypt can add.

View on GitHub (pinned to c9a67b23e8)

Solutions

  1. Restore the object from a backup or re-upload it; the stored bytes are structurally invalid
  2. Compare the object size and first bytes against a known-good object to confirm truncation
  3. Verify the encryption key file is the same one used at write time (a mismatched RSA key decrypts the data key to garbage lengths)
  4. Check network/proxy completeness (Content-Length) if the object is streamed through a gateway
Defensive patterns

Strategy: try-catch

Validate before calling

if len(ciphertext) >= 3 {
	k := int(ciphertext[0])<<8 | int(ciphertext[1]); n := int(ciphertext[2])
	if 3+k+n >= len(ciphertext) { /* malformed: restore object */ }
}

Try / catch

plain, err := enc.Decrypt(ciphertext)
if err != nil && strings.Contains(err.Error(), "malformed ciphertext") {
	// header corrupt or truncated object: re-sync from source
}

Prevention

When it happens

Trigger: Calling Read on an object whose bytes were truncated right after (or inside) the encrypted-key/nonce header, or whose header bytes were corrupted to encode absurd lengths.

Common situations: Partial object upload or truncated download; bit rot or manual edit of the object in object storage; attempting to decrypt data encrypted with a different format/version; endianness/layout mismatch from another tool.

Understand the failure class

Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.

Related errors


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/e4c65a0c9e1cefe3. Report an issue: GitHub.