-
Notifications
You must be signed in to change notification settings - Fork 64
Description
Summary:
ACCP enforces strict buffer requirements when decrypting data using AES/CBC with padding (e.g., PKCS5Padding
, PKCS7Padding
). Specifically, it requires that the output buffer passed to Cipher.update()
be large enough to hold the full decrypted plaintext output, including the final block that contains padding, as mentioned here in the code.
While this behavior is fully compliant with the JCA specification, it differs from the behavior of the default Java provider (SunJCE), which will buffer the final block internally and defer writing it until Cipher.doFinal()
is called. SunJCE will subtract the padding when performing buffer size checks. This difference in behavior leads to compatibility issues with existing applications, including https://github.com/trinodb/trino.
Trino contains logic in the CompressingDecryptingPageDeserializer class that calls Cipher.update()
during page deserialization with an output buffer sized based on assumptions consistent with SunJCE.
More specifically, it passes a buffer with a size equal to the size of the data to be decrypted, meaning it will get a java.crypto.ShortBufferException
when padding is required.
Tests such as TestPagesSerde.testDeserializationWithRollover will fail due to this assumption.
This difference is not documented in the DIFFERENCES.md
and it violates the statement made in the README.md
This means that it can be used as a drop in replacement for many different Java applications
The described behavior difference stems from how ACCP integrates with aws-lc
native code and should be remedied. ACCP must always provide drop-in compatibility with JDK.
Reproducing the error in Trino:
- Clone Trino and checkout
- Build with
./mvnw clean install -DskipTests
- Run the failing test
./mvnw test -Dtest=io.trino.execution.buffer.TestPagesSerde -pl core/trino-main
Or, the following code sample highlights the difference where ACCP will throw a ShortBufferException
while SunJCE will not:
byte[] keyBytes = new byte[16];
byte[] ivBytes = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(keyBytes);
random.nextBytes(ivBytes);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec iv = new IvParameterSpec(ivBytes);
byte[] plaintext = new byte[32];
random.nextBytes(plaintext);
Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] ciphertext = encryptCipher.doFinal(plaintext);
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
decryptCipher.init(Cipher.DECRYPT_MODE, key, iv);
int outputSize = decryptCipher.getOutputSize(ciphertext.length - 10); // buffer.size < size + padding block
byte[] outputBuffer = new byte[outputSize];
int updateLen = decryptCipher.update(ciphertext, 0, ciphertext.length, outputBuffer, 0); // ACCP throws exception here
int finalLen = decryptCipher.doFinal(outputBuffer, updateLen);
int totalLen = updateLen + finalLen;
byte[] decrypted = Arrays.copyOf(outputBuffer, totalLen);
System.out.println("Decryption successful: " + Arrays.equals(plaintext, decrypted));