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
- Restore the object from a backup or re-upload it; the stored bytes are structurally invalid
- Compare the object size and first bytes against a known-good object to confirm truncation
- Verify the encryption key file is the same one used at write time (a mismatched RSA key decrypts the data key to garbage lengths)
- 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
- Verify the RSA key file matches the format-time key before mounting
- Guard against partial uploads; re-run interrupted syncs
- Enable checksum verification on backup/restore pipelines
- Avoid manual edits of objects in the storage console
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.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- received encrypted text length is less than 3, the object is
- Decrypt: %s
- Decrypt: truncated chunk header
- Decrypt: chunk data truncated: need %d, have %d
- Decrypt: %s
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/e4c65a0c9e1cefe3.
Report an issue: GitHub.