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

davsclaus pushed a commit to branch camel-4.22.x
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/camel-4.22.x by this push:
     new 837b0650cdb2 CAMEL-24440: camel-crypto - per-message IV when inlining, 
and uniform authentication failures
837b0650cdb2 is described below

commit 837b0650cdb2b249b5cc1aef0d328422e61537d8
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 8 15:31:10 2026 +0200

    CAMEL-24440: camel-crypto - per-message IV when inlining, and uniform 
authentication failures
    
    Backport of #26725 to camel-4.22.x.
    
    CryptoDataFormat now generates a per-message initialization vector when
    inlining is enabled without a configured vector. It reports a single
    "Message authentication failed" error for both bad padding and MAC
    mismatch, so a tampered message no longer works as a padding oracle. The
    failure message no longer includes the MAC values, and the length of the
    inlined vector is now bounded. None of these changes affects the format
    of data that was already written.
    
    The upgrade note goes into the 4.22.2 section of the 4.22 upgrade guide.
    CryptoDataFormatLargePayloadTest is restored to match main, since it
    expected the old MAC mismatch message.
    
    Closes #27490
    
    Co-Authored-By: Andrea Cosentino <[email protected]>
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 .../camel/converter/crypto/CryptoDataFormat.java   |  75 +++++++-
 .../camel/converter/crypto/HMACAccumulator.java    |  16 +-
 .../crypto/CryptoDataFormatIvAndFailureTest.java   | 210 +++++++++++++++++++++
 .../crypto/CryptoDataFormatLargePayloadTest.java   |   7 +-
 .../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc    |  26 +++
 5 files changed, 322 insertions(+), 12 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 e1deb80f29f4..e0b7181fffa4 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/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/CryptoDataFormatLargePayloadTest.java
 
b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/CryptoDataFormatLargePayloadTest.java
index 4e2196041764..20fc659df409 100644
--- 
a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/CryptoDataFormatLargePayloadTest.java
+++ 
b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/CryptoDataFormatLargePayloadTest.java
@@ -27,9 +27,9 @@ import org.apache.camel.test.junit6.CamelTestSupport;
 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.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
  * The HMAC (appended by default) is split off in a circular buffer on 
unmarshal. The buffer must wrap around for any
@@ -71,15 +71,14 @@ public class CryptoDataFormatLargePayloadTest extends 
CamelTestSupport {
         // drop the last cipher block (DES has 8 byte blocks)
         byte[] truncated = Arrays.copyOf(encrypted, encrypted.length - 8);
 
-        // on this branch a truncated message fails on decryption (padding) 
before the mac is checked
-        assertThrows(CamelExecutionException.class, () -> 
template.requestBody("direct:unmarshal", truncated));
+        assertAuthenticationFailed(truncated);
     }
 
     private void assertAuthenticationFailed(byte[] encrypted) {
         CamelExecutionException e
                 = assertThrows(CamelExecutionException.class, () -> 
template.requestBody("direct:unmarshal", encrypted));
         IllegalStateException cause = 
assertInstanceOf(IllegalStateException.class, e.getCause());
-        assertTrue(cause.getMessage().startsWith("Expected mac did not match 
actual mac"), cause.getMessage());
+        assertEquals(HMACAccumulator.AUTHENTICATION_FAILED, 
cause.getMessage());
     }
 
     private void doRoundTrip(int size) throws Exception {
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index 3dab963a9edd..8c2ad20d563e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -47,6 +47,32 @@ applied does not receive the messages of the producers, and 
it is closed when it
 A WebSocket client that connected without satisfying the configured security 
provider or allowed roles is now
 rejected: it must authenticate, or the option must be removed from the 
WebSocket endpoint.
 
+=== 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.
+
 == Upgrading from 4.22.0 to 4.22.1
 
 === camel-hazelcast - ReplicatedHazelcastAggregationRepository now applies the 
default serialization filter

Reply via email to