apache/cassandra · error · IllegalStateException

could not read encrypted blocked metadata header

Error message

could not read encrypted blocked metadata header

What it means

EncryptionUtils.decrypt reads the fixed-size encrypted block metadata header (two ints: encrypted and plaintext lengths) from the channel. If the channel yields fewer bytes than ENCRYPTED_BLOCK_HEADER_SIZE, the data is truncated/corrupt and IllegalStateException is thrown.

Solutions

  1. Treat the segment as truncated: verify file size against the segment's recorded sync position / commit log metadata.
  2. Restore the segment from backup or rely on unflushed data loss handling (commit logs are restart-safe; partial tails are skipped at startup).
  3. Check storage health (disk-full, filesystem errors) that could have truncated writes.
  4. If in tests, ensure all encrypted blocks were fully flushed and the channel position is valid before decrypting.

Example fix

// before: reading a partially written segment tail
ByteBuffer out = EncryptionUtils.decrypt(channel, null, true, reusableBuffers);
// after: bound reads to the committed length
long readableEnd = Math.min(channel.size(), committedSyncPosition);
if (channel.position() + EncryptionUtils.ENCRYPTED_BLOCK_HEADER_SIZE > readableEnd) return null; // no more complete blocks
ByteBuffer out = EncryptionUtils.decrypt(channel, null, true, reusableBuffers);
Defensive patterns

Strategy: validation

Validate before calling

long remaining = committedEnd - channel.position();
if (remaining < EncryptionUtils.ENCRYPTED_BLOCK_HEADER_SIZE)
    return null; // truncated tail: no complete block left, stop reading instead of decrypting

Try / catch

try {
    ByteBuffer out = EncryptionUtils.decrypt(channel, null, true, reusableBuffers);
} catch (IllegalStateException e) {
    if (e.getMessage().contains("metadata header")) {
        // truncated/corrupt segment: mark segment end, stop replay
    }
}

Prevention

When it happens

Trigger: decrypt() reading from a commit log segment that ends before a complete 16-byte header — truncated file, zero-padded tail, or reading past the last valid block.

Common situations: Disk-full during commit log write, abrupt crash leaving a partially written segment, or a file copied/truncated at the wrong size.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/e1472652bde19e2c. Report an issue: GitHub.

Appendix: source

Thrown at src/java/org/apache/cassandra/security/EncryptionUtils.java:145

     * Decrypt the input data, as well as manage sizing of the {@code outputBuffer}; if the buffer is not big enough,
     * deallocate current, and allocate a large enough buffer.
     *
     * @return the byte buffer that was actaully written to; it may be the {@code outputBuffer} if it had enough capacity,
     * or it may be a new, larger instance. Callers should capture the return buffer (if calling multiple times).
     */
    public static ByteBuffer decrypt(ReadableByteChannel channel, ByteBuffer outputBuffer, boolean allowBufferResize, Cipher cipher) throws IOException
    {
        ByteBuffer metadataBuffer = reusableBuffers.get();
        if (metadataBuffer.capacity() < ENCRYPTED_BLOCK_HEADER_SIZE)
        {
            metadataBuffer = ByteBufferUtil.ensureCapacity(metadataBuffer, ENCRYPTED_BLOCK_HEADER_SIZE, true);
            reusableBuffers.set(metadataBuffer);
        }

        metadataBuffer.position(0).limit(ENCRYPTED_BLOCK_HEADER_SIZE);
        channel.read(metadataBuffer);
        if (metadataBuffer.remaining() < ENCRYPTED_BLOCK_HEADER_SIZE)
            throw new IllegalStateException("could not read encrypted blocked metadata header");
        int encryptedLength = metadataBuffer.getInt();
        // this is the length of the compressed data
        int plainTextLength = metadataBuffer.getInt();

        outputBuffer = ByteBufferUtil.ensureCapacity(outputBuffer, Math.max(plainTextLength, encryptedLength), allowBufferResize);
        outputBuffer.position(0).limit(encryptedLength);
        channel.read(outputBuffer);

        ByteBuffer dupe = outputBuffer.duplicate();
        dupe.clear();

        try
        {
            cipher.doFinal(outputBuffer, dupe);
        }
        catch (ShortBufferException | IllegalBlockSizeException | BadPaddingException e)
        {
            throw new IOException("failed to decrypt commit log block", e);

View on GitHub (pinned to 88fd0f6a0e)