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
- 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.
- Inspect the wrapped exception: BadPaddingException/IllegalBlockSizeException usually indicate wrong key or corrupted ciphertext.
- Check the IV used to build the decryptor matches the block's header IV.
- 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
- Retain all key versions referenced by existing encrypted segments
- Freeze TDE config (cipher, key_alias) for the lifetime of existing segments
- Verify checksums/integrity when moving encrypted files between hosts
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
- could not read encrypted blocked metadata header
- Cannot safely construct descriptor for segment, as name…
- Cannot safely construct descriptor for segment, either from…
- commitlog_disk_access_mode =
- Encountered bad header at position
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)