apache/cassandra · error · IOException

failed to decrypt commit log block

Error message

failed to decrypt commit log block

What it means

EncryptionUtils.decrypt finishes the JCE decryption of a block with cipher.doFinal; failures (short output buffer, illegal block size, bad padding) are wrapped in IOException("failed to decrypt commit log block"). This almost always means the ciphertext, key, or IV does not match what was encrypted.

Solutions

  1. Confirm the current key_alias and cipher configuration match those used when the segment was written; restore the old key if it was rotated out.
  2. Inspect the wrapped exception: BadPaddingException/IllegalBlockSizeException usually indicate wrong key or corrupted ciphertext.
  3. Check the IV used to build the decryptor matches the block's header IV.
  4. If the segment is damaged, restore from backup; partial block corruption is not recoverable.

Example fix

// before: config changed after segments were written
transparent_data_encryption_options:
  key_alias: key_v2
// after: keep prior keys available to decrypt old segments
transparent_data_encryption_options:
  key_alias: key_v2
# ensure key_v1 still exists in the keystore for old commit log segments
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure the key for the segment's alias is still loadable before replaying old segments
KeyStore ks = KeyStore.getInstance("JCEKS");
try (InputStream in = new FileInputStream(keystorePath)) { ks.load(in, password.toCharArray()); }
if (!ks.containsAlias(tdeOptions.key_alias)) throw new IllegalStateException("key removed: " + tdeOptions.key_alias);

Try / catch

try {
    ByteBuffer plain = EncryptionUtils.decrypt(channel, null, true, reusableBuffers);
} catch (IOException e) {
    if (e.getMessage().equals("failed to decrypt commit log block")) {
        // e.getCause() BadPadding/IllegalBlockSize -> wrong key/IV or corrupt block; do not retry with same inputs
    }
}

Prevention

When it happens

Trigger: decrypt() with a cipher built for a different key_alias/IV than the block was written with, corrupted ciphertext, or a dupe/output buffer too small for the plaintext.

Common situations: Key rotation removing a still-needed key version, decrypting a segment with a changed transparent_data_encryption_options configuration (cipher or key_alias), or media corruption of the encrypted block.

Related errors


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

Appendix: source

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

            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);
        }

        dupe.position(0).limit(plainTextLength);
        return dupe;
    }

    // path used when decrypting commit log files
    public static ByteBuffer decrypt(FileDataInput fileDataInput, ByteBuffer outputBuffer, boolean allowBufferResize, Cipher cipher) throws IOException
    {
        return decrypt(new DataInputReadChannel(fileDataInput), outputBuffer, allowBufferResize, cipher);
    }

    /**
     * Uncompress 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).

View on GitHub (pinned to 88fd0f6a0e)