This is an automated email from the ASF dual-hosted git repository.

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 4ebacf60e46c CAMEL-24440: camel-crypto - per-message IV when inlining, 
and uniform authentication failures (#26725)
4ebacf60e46c is described below

commit 4ebacf60e46cc3e2d74a07076cabb7c9e268713a
Author: Andrea Cosentino <[email protected]>
AuthorDate: Fri Sep 25 11:25:50 2026 +0200

    CAMEL-24440: camel-crypto - per-message IV when inlining, and uniform 
authentication failures (#26725)
    
    * CAMEL-24440: camel-crypto - per-message IV when inlining, and uniform 
authentication failures
    
    Three changes to CryptoDataFormat, none of which alters the format of data 
already
    written.
    
    Per-message initialization vector. marshal() threw "Inlining cannot be 
performed, as no
    initialization vector was specified" whenever 
shouldInlineInitializationVector was set
    without a statically configured vector, which pushed routes into 
configuring one and
    reusing it for every message - the thing inlining exists to avoid. A fresh 
vector is now
    generated per message when none is supplied. The vector is written into the 
message, so
    readers take it from the stream and need no change; a vector supplied 
explicitly, by
    configuration or by the CamelCryptoInitVector header, is still used as 
given.
    
    Uniform authentication failure. A tampered message surfaced two 
distinguishable
    outcomes - "Given final block not properly padded" from CipherInputStream, 
or "Expected
    mac did not match actual mac" from the MAC check - and a caller who can 
submit
    ciphertext and observe which one came back can use that to recover 
plaintext. Both now
    report "Message authentication failed". Checked on JDK 21 that 
CipherInputStream does
    surface bad padding as an IOException wrapping BadPaddingException, since 
older JDKs
    swallowed it.
    
    MAC values out of the failure message. It carried both MACs in hex, and the 
computed one
    is HMAC_k over the plaintext just produced, so it handed the caller a value 
they could
    not otherwise compute.
    
    Also bound the inlined vector length, which was read from the message and 
used directly
    to size an allocation.
    
    Left alone: the HMAC key is still derived from the same material as the 
cipher key.
    Separating them changes the MAC written into the message, so neither 
direction stays
    readable across versions - that needs an opt-in or a format version marker 
and is
    tracked on the issue, along with the MAC-then-encrypt composition, where 
validating
    before streaming would mean buffering the whole plaintext.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    * CAMEL-24440: camel-crypto - cache the cipher block size and clarify the 
failure-reporting test
    
    Addresses review feedback on #26725:
    - generateInitializationVector no longer builds a throwaway Cipher on every
      marshal just to read the block size; the size is fixed by the algorithm, 
so
      it is looked up once and cached.
    - clarified the failure-reporting test's comment: the MAC is encrypted 
inside
      the ciphertext (written through the CipherOutputStream), so flipping the 
last
      byte corrupts the final block's padding - the BadPaddingException path - 
which
      the test already exercises.
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    * CAMEL-24440: camel-crypto - address review on inline IV, MAC-off 
failures, and test style
    
    Addresses davsclaus's review on #26725:
    - inlining with a configured algorithmParameterSpec now fails loudly 
instead of
      generating a per-message IV the cipher ignores (the spec wins in
      initializeCipher), which silently reused one IV across every message.
    - the BadPaddingException -> "Message authentication failed" rewrite is now 
gated
      on shouldAppendHMAC: with the MAC off nothing is authenticating, so the 
real
      cipher cause is surfaced (at debug) rather than misdescribed.
    - moved MAX_INLINE_IV_LENGTH and SECURE_RANDOM up with the other class 
fields.
    - imported java.util.Arrays and java.io.ByteArrayInputStream in the test 
rather
      than using fully-qualified names (OpenRewrite / CI uncommitted-changes 
check).
    - added tests for both behaviour fixes, each verified to fail against the
      un-fixed code.
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    * Update 
docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
    
    Co-authored-by: Guillaume Nodet - AI Bot <[email protected]>
    
    ---------
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
    Co-authored-by: Guillaume Nodet - AI Bot <[email protected]>
---
 .../camel/converter/crypto/CryptoDataFormat.java   |  75 +++++++-
 .../camel/converter/crypto/HMACAccumulator.java    |  16 +-
 .../crypto/CryptoDataFormatIvAndFailureTest.java   | 210 +++++++++++++++++++++
 .../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc    |  25 +++
 4 files changed, 318 insertions(+), 8 deletions(-)

diff --git 
a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/CryptoDataFormat.java
 
b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/CryptoDataFormat.java
index eb0449959608..09a0764b5a1e 100644
--- 
a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/CryptoDataFormat.java
+++ 
b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/CryptoDataFormat.java
@@ -21,7 +21,9 @@ import java.io.DataOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.GeneralSecurityException;
 import java.security.Key;
+import java.security.SecureRandom;
 import java.security.spec.AlgorithmParameterSpec;
 
 import javax.crypto.Cipher;
@@ -72,6 +74,13 @@ public class CryptoDataFormat extends ServiceSupport 
implements DataFormat, Data
 
     private static final Logger LOG = 
LoggerFactory.getLogger(CryptoDataFormat.class);
     private static final String INIT_VECTOR = "CamelCryptoInitVector";
+    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
+    /**
+     * Upper bound on the length of an inlined initialization vector read from 
the stream. A JCE initialization vector
+     * is at most a cipher block, so this is generous; the bound exists 
because the length is read from the message and
+     * used directly to size an allocation.
+     */
+    private static final int MAX_INLINE_IV_LENGTH = 1024;
     private String algorithm;
     private String cryptoProvider;
     private Key key;
@@ -81,6 +90,9 @@ public class CryptoDataFormat extends ServiceSupport 
implements DataFormat, Data
     private String macAlgorithm = "HmacSHA1";
     private boolean shouldAppendHMAC = true;
     private AlgorithmParameterSpec algorithmParameterSpec;
+    // The cipher block size is fixed by the algorithm, so it is looked up 
once and cached rather than building a
+    // Cipher on every marshal purely to read it.
+    private volatile int cachedBlockSize;
 
     public CryptoDataFormat() {
     }
@@ -124,6 +136,20 @@ public class CryptoDataFormat extends ServiceSupport 
implements DataFormat, Data
     @Override
     public void marshal(Exchange exchange, Object graph, OutputStream 
outputStream) throws Exception {
         byte[] iv = getInitializationVector(exchange);
+        if (iv == null && inline) {
+            if (algorithmParameterSpec != null) {
+                // initializeCipher gives algorithmParameterSpec precedence 
over the IV, so a generated vector would be
+                // written into the message but never used - every message 
would encrypt identically behind a vector
+                // that only looks per-message. Keep failing loudly, as this 
configuration did before.
+                throw new IllegalStateException(
+                        "Inlining cannot be performed when an 
algorithmParameterSpec is configured, as the spec is"
+                                                + " used instead of the 
initialization vector");
+            }
+            // The whole point of inlining is that the IV travels with the 
message, so there is no reason to make
+            // the caller supply a fixed one - and requiring it is what used 
to push users into reusing a single IV
+            // across every message.
+            iv = generateInitializationVector();
+        }
         Key key = getKey(exchange);
 
         InputStream plaintextStream = 
ExchangeHelper.convertToMandatoryType(exchange, InputStream.class, graph);
@@ -166,8 +192,23 @@ public class CryptoDataFormat extends ServiceSupport 
implements DataFormat, Data
                 byte[] buffer = new byte[bufferSize];
                 hmac.attachStream(osb);
                 int read;
-                while ((read = cipherStream.read(buffer)) >= 0) {
-                    hmac.decryptUpdate(buffer, read);
+                try {
+                    while ((read = cipherStream.read(buffer)) >= 0) {
+                        hmac.decryptUpdate(buffer, read);
+                    }
+                } catch (IOException e) {
+                    if (shouldAppendHMAC && e.getCause() instanceof 
GeneralSecurityException) {
+                        // CipherInputStream surfaces bad padding as an 
IOException wrapping
+                        // BadPaddingException, while a bad MAC surfaces from 
validate() below. Reporting the two
+                        // differently is exactly what lets a caller who can 
submit ciphertext and watch the
+                        // outcome tell them apart, which is the 
padding-oracle distinguisher. Report the same
+                        // authentication failure for both - but only when a 
MAC is actually appended: with
+                        // shouldAppendHMAC=false nothing is authenticating, 
so calling it an authentication failure
+                        // would misdescribe a plain padding error and drop 
its cause.
+                        LOG.debug("Reporting cipher failure as an 
authentication failure", e);
+                        throw new 
IllegalStateException(HMACAccumulator.AUTHENTICATION_FAILED);
+                    }
+                    throw e;
                 }
                 hmac.validate();
                 return osb.build();
@@ -207,6 +248,11 @@ public class CryptoDataFormat extends ServiceSupport 
implements DataFormat, Data
         if (inline) {
             try {
                 int ivLength = new DataInputStream(encryptedStream).readInt();
+                if (ivLength < 0 || ivLength > MAX_INLINE_IV_LENGTH) {
+                    throw new IOException(
+                            String.format("Inlined initialization vector 
length '%d' is not between 0 and %d",
+                                    ivLength, MAX_INLINE_IV_LENGTH));
+                }
                 iv = new byte[ivLength];
                 int read = encryptedStream.read(iv);
                 if (read != ivLength) {
@@ -247,6 +293,31 @@ public class CryptoDataFormat extends ServiceSupport 
implements DataFormat, Data
         };
     }
 
+    /**
+     * A fresh initialization vector, sized to the cipher's block length. Only 
used when the vector is inlined into the
+     * message, so the reader takes it from the stream and nothing needs to be 
shared out of band.
+     */
+    private byte[] generateInitializationVector() throws Exception {
+        byte[] iv = new byte[getCipherBlockSize()];
+        SECURE_RANDOM.nextBytes(iv);
+        return iv;
+    }
+
+    private int getCipherBlockSize() throws Exception {
+        int blockSize = cachedBlockSize;
+        if (blockSize == 0) {
+            Cipher cipher
+                    = cryptoProvider == null ? Cipher.getInstance(algorithm) : 
Cipher.getInstance(algorithm, cryptoProvider);
+            blockSize = cipher.getBlockSize();
+            if (blockSize <= 0) {
+                // A stream cipher reports no block size; 16 bytes is the 
usual nonce length
+                blockSize = 16;
+            }
+            cachedBlockSize = blockSize;
+        }
+        return blockSize;
+    }
+
     private byte[] getInitializationVector(Exchange exchange) {
         byte[] iv = exchange.getIn().getHeader(INIT_VECTOR, byte[].class);
         if (iv == null) {
diff --git 
a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/HMACAccumulator.java
 
b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/HMACAccumulator.java
index a1482c3bb0ed..96bd1df07b34 100644
--- 
a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/HMACAccumulator.java
+++ 
b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/HMACAccumulator.java
@@ -24,8 +24,6 @@ import java.security.MessageDigest;
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 
-import static org.apache.camel.converter.crypto.HexUtils.byteArrayToHexString;
-
 /**
  * <code>HMACAccumulator</code> is used to build Hash Message Authentication 
Codes. It has two modes, one where all the
  * data acquired is used to build the MAC and a second that assumes that the 
last n bytes of the acquired data will
@@ -96,15 +94,21 @@ public class HMACAccumulator {
         return appended;
     }
 
+    /**
+     * The single message reported for every authentication failure. Bad 
padding and a bad MAC must be indistinguishable
+     * to a caller who can submit ciphertext and observe the outcome, because 
telling them apart is what turns a CBC
+     * decryption into a padding oracle.
+     */
+    static final String AUTHENTICATION_FAILED = "Message authentication 
failed";
+
     public void validate() {
         byte[] actual = getCalculatedMac();
         byte[] expected = getAppendedMac();
         // Use a constant-time comparison to avoid leaking MAC-match progress 
through timing (side-channel).
         if (!MessageDigest.isEqual(expected, actual)) {
-            throw new IllegalStateException(
-                    "Expected mac did not match actual mac\nexpected:"
-                                            + byteArrayToHexString(expected) + 
"\n     actual:"
-                                            + byteArrayToHexString(actual));
+            // The computed MAC is HMAC_k over the plaintext that was just 
produced, so reporting it hands the
+            // caller a value they could not otherwise compute. Neither MAC 
belongs in the message.
+            throw new IllegalStateException(AUTHENTICATION_FAILED);
         }
     }
 
diff --git 
a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/CryptoDataFormatIvAndFailureTest.java
 
b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/CryptoDataFormatIvAndFailureTest.java
new file mode 100644
index 000000000000..a5c9ff59d717
--- /dev/null
+++ 
b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/CryptoDataFormatIvAndFailureTest.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.converter.crypto;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.Key;
+import java.util.Arrays;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.spec.IvParameterSpec;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class CryptoDataFormatIvAndFailureTest {
+
+    private static final String PAYLOAD = "the quick brown fox jumps over the 
lazy dog";
+
+    /**
+     * Inlining exists so the initialization vector travels with the message. 
Requiring a statically configured one as
+     * well is what pushed routes into reusing a single vector for every 
message.
+     */
+    @Test
+    void inliningGeneratesAFreshInitializationVectorPerMessage() throws 
Exception {
+        Key key = key();
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+            CryptoDataFormat encryptor = new 
CryptoDataFormat("AES/CBC/PKCS5Padding", key);
+            encryptor.setShouldInlineInitializationVector(true);
+
+            byte[] first = marshal(context, encryptor, PAYLOAD);
+            byte[] second = marshal(context, encryptor, PAYLOAD);
+
+            assertFalse(Arrays.equals(first, second),
+                    "the same plaintext must not produce identical ciphertext 
twice");
+
+            CryptoDataFormat decryptor = new 
CryptoDataFormat("AES/CBC/PKCS5Padding", key);
+            decryptor.setShouldInlineInitializationVector(true);
+            assertEquals(PAYLOAD, unmarshal(context, decryptor, first));
+            assertEquals(PAYLOAD, unmarshal(context, decryptor, second));
+        }
+    }
+
+    /**
+     * A caller who can submit ciphertext and observe the outcome must not be 
able to tell a padding failure from a MAC
+     * failure - telling them apart is what turns CBC decryption into a 
padding oracle.
+     */
+    @Test
+    void badPaddingAndBadMacAreReportedIdentically() throws Exception {
+        Key key = key();
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+            // a static vector, not inlining, so this exercises the failure 
reporting and nothing else
+            CryptoDataFormat format = new 
CryptoDataFormat("AES/CBC/PKCS5Padding", key);
+            format.setInitVector(new byte[16]);
+
+            byte[] ciphertext = marshal(context, format, PAYLOAD);
+
+            // The MAC is written through the CipherOutputStream during 
marshal, so it is encrypted inside the
+            // ciphertext, not appended in the clear. The last wire byte is 
therefore the final ciphertext block, and
+            // flipping it makes that block no longer decrypt to valid PKCS5 
padding - the BadPaddingException path,
+            // distinct from the bad-MAC path exercised just below.
+            byte[] badPadding = ciphertext.clone();
+            badPadding[badPadding.length - 1] ^= 0x01;
+
+            // corrupt a byte in the middle: padding still validates, the 
appended MAC does not
+            byte[] badMac = ciphertext.clone();
+            badMac[badMac.length / 2] ^= 0x01;
+
+            String paddingFailure = failureMessage(context, format, 
badPadding);
+            String macFailure = failureMessage(context, format, badMac);
+
+            assertEquals(macFailure, paddingFailure, "the two failures must be 
indistinguishable");
+            assertTrue(paddingFailure.contains("authentication failed"), 
"unexpected message: " + paddingFailure);
+        }
+    }
+
+    /**
+     * The inlined length is read from the message and used to size an 
allocation, so it has to be bounded.
+     */
+    @Test
+    void anOversizedInlinedInitializationVectorLengthIsRejected() throws 
Exception {
+        Key key = key();
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+            CryptoDataFormat decryptor = new 
CryptoDataFormat("AES/CBC/PKCS5Padding", key);
+            decryptor.setShouldInlineInitializationVector(true);
+
+            // a four byte length of 0x7FFFFFFF followed by nothing
+            byte[] hostile = { 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF };
+
+            Exception e = assertThrows(Exception.class, () -> 
unmarshal(context, decryptor, hostile));
+            assertTrue(rootMessage(e).contains("is not between 0 and"), 
"unexpected message: " + rootMessage(e));
+        }
+    }
+
+    @Test
+    void aRoundTripWithAStaticVectorStillWorks() throws Exception {
+        Key key = key();
+        byte[] iv = new byte[16];
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+            CryptoDataFormat format = new 
CryptoDataFormat("AES/CBC/PKCS5Padding", key);
+            format.setInitVector(iv);
+
+            byte[] ciphertext = marshal(context, format, PAYLOAD);
+            assertEquals(PAYLOAD, unmarshal(context, format, ciphertext));
+            assertArrayEquals(iv, format.getInitVector());
+        }
+    }
+
+    /**
+     * Inlining plus a configured algorithmParameterSpec used to be a silent 
IV-reuse trap: the spec wins in the cipher,
+     * so a generated per-message vector was written into the message but 
never used. It must fail loudly.
+     */
+    @Test
+    void inliningWithAnAlgorithmParameterSpecIsRejected() throws Exception {
+        Key key = key();
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+            CryptoDataFormat format = new 
CryptoDataFormat("AES/CBC/PKCS5Padding", key);
+            format.setShouldInlineInitializationVector(true);
+            format.setAlgorithmParameterSpec(new IvParameterSpec(new 
byte[16]));
+
+            IllegalStateException e = assertThrows(IllegalStateException.class,
+                    () -> marshal(context, format, PAYLOAD));
+            assertTrue(e.getMessage().contains("algorithmParameterSpec"), 
"unexpected message: " + e.getMessage());
+        }
+    }
+
+    /**
+     * With the MAC turned off there is nothing authenticating, so a cipher 
failure must surface as itself rather than
+     * be relabelled "Message authentication failed" (which would also drop 
the real cause).
+     */
+    @Test
+    void aCipherFailureWithoutAMacIsNotReportedAsAnAuthenticationFailure() 
throws Exception {
+        Key key = key();
+        try (DefaultCamelContext context = new DefaultCamelContext()) {
+            context.start();
+            CryptoDataFormat format = new 
CryptoDataFormat("AES/CBC/PKCS5Padding", key);
+            format.setInitVector(new byte[16]);
+            format.setShouldAppendHMAC(false);
+
+            byte[] ciphertext = marshal(context, format, PAYLOAD);
+            byte[] corrupted = ciphertext.clone();
+            corrupted[corrupted.length - 1] ^= 0x01;
+
+            String message = failureMessage(context, format, corrupted);
+            assertFalse(message.contains("authentication failed"),
+                    "a cipher failure with no MAC must not be relabelled an 
authentication failure: " + message);
+        }
+    }
+
+    private static String failureMessage(DefaultCamelContext context, 
CryptoDataFormat format, byte[] body) {
+        Exception e = assertThrows(Exception.class, () -> unmarshal(context, 
format, body));
+        return rootMessage(e);
+    }
+
+    private static String rootMessage(Throwable t) {
+        while (t.getCause() != null) {
+            t = t.getCause();
+        }
+        return String.valueOf(t.getMessage());
+    }
+
+    private static byte[] marshal(DefaultCamelContext context, 
CryptoDataFormat format, String payload)
+            throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        format.marshal(exchange, payload.getBytes(StandardCharsets.UTF_8), 
out);
+        return out.toByteArray();
+    }
+
+    private static String unmarshal(DefaultCamelContext context, 
CryptoDataFormat format, byte[] body)
+            throws Exception {
+        Exchange exchange = new DefaultExchange(context);
+        Object result = format.unmarshal(exchange, new 
ByteArrayInputStream(body));
+        return context.getTypeConverter().convertTo(String.class, exchange, 
result);
+    }
+
+    private static Key key() throws Exception {
+        KeyGenerator generator = KeyGenerator.getInstance("AES");
+        generator.init(128);
+        return generator.generateKey();
+    }
+}
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index fdfc1d31cc00..7e9e6024b2e4 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -2844,6 +2844,31 @@ are unaffected. Routes that set the header by its 
literal string name, or that u
 `allowTemplateFromHeader=true` with the old header names, must switch to the 
new `Camel`-prefixed
 names.
 
+=== camel-crypto
+
+Three changes to `CryptoDataFormat`, none of which affects the format of data 
already written.
+
+*A per-message initialization vector when inlining.* `marshal` used to throw
+`Inlining cannot be performed, as no initialization vector was specified` when
+`shouldInlineInitializationVector` was set without a statically configured 
vector — which pushed routes
+into reusing one vector for every message, the thing inlining exists to avoid. 
A fresh vector is now
+generated per message when none is supplied. Because the vector is written 
into the message, readers pick
+it up from the stream and need no change. A vector supplied explicitly, by 
configuration or by the
+`CamelCryptoInitVector` header, is still used as given.
+
+*Authentication failures report uniformly.* A tampered message previously 
surfaced two distinguishable
+outcomes: `Given final block not properly padded` from the cipher, or 
`Expected mac did not match actual
+mac` from the MAC check. A caller able to submit ciphertext and observe which 
one came back can use that
+distinction to recover plaintext. Both now report `Message authentication 
failed`, and the message no
+longer includes the expected and computed MAC values — the computed one is 
`HMAC_k` over the plaintext
+just produced. Code matching on the old text must be updated.
+
+*The inlined vector length is bounded.* The length prefix is read from the 
message and used to size an
+allocation; a declared length outside 0–1024 is now rejected instead of 
attempted.
+
+Not changed: the HMAC key is still derived from the same key material as the 
cipher. Separating them
+would change the MAC written into the message and so could not be read by 
earlier versions; that is
+tracked separately.
 === camel-debezium - a failed embedded engine is now reported
 
 The Debezium consumers now register a `CompletionCallback` on the embedded 
engine. When the engine stops

Reply via email to