Move simple decryption cases out of LoadBalancerConfig.

Item for unencrypted/3Pass/1Pass
Tip of tree: 23.0/103/33.9
This CL: 21.1/104/33.3

This partially reverts cl/597301121 but keeps its large gains for 3-pass decryption.

PiperOrigin-RevId: 599637882
diff --git a/quiche/quic/load_balancer/load_balancer_config.cc b/quiche/quic/load_balancer/load_balancer_config.cc
index 726d07e..2373e6c 100644
--- a/quiche/quic/load_balancer/load_balancer_config.cc
+++ b/quiche/quic/load_balancer/load_balancer_config.cc
@@ -8,12 +8,10 @@
 #include <cstring>
 #include <optional>
 
-#include "absl/numeric/int128.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "openssl/aes.h"
 #include "quiche/quic/core/quic_connection_id.h"
-#include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/load_balancer/load_balancer_server_id.h"
 #include "quiche/quic/platform/api/quic_bug_tracker.h"
 
@@ -91,25 +89,10 @@
              : std::optional<LoadBalancerConfig>();
 }
 
-LoadBalancerServerId LoadBalancerConfig::Decrypt(
+LoadBalancerServerId LoadBalancerConfig::FourPassDecrypt(
     absl::Span<const uint8_t> ciphertext) const {
-  if (ciphertext.length() < total_len()) {
-    return LoadBalancerServerId();
-  }
   if (!key_.has_value()) {
-    return LoadBalancerServerId(
-        absl::Span<const uint8_t>(ciphertext.data() + 1, server_id_len_));
-  }
-  if (plaintext_len() == kLoadBalancerBlockSize) {
-    if (!block_decrypt_key_.has_value()) {
-      QUIC_BUG(quic_bug_596735037_01) << "Block decrypt key is not set.";
-      return LoadBalancerServerId();
-    }
-    uint8_t plaintext[kLoadBalancerBlockSize];
-    AES_decrypt(ciphertext.subspan(1, kLoadBalancerBlockSize).data(), plaintext,
-                &*block_decrypt_key_);
-    return LoadBalancerServerId(
-        absl::Span<const uint8_t>(plaintext, server_id_len_));
+    return LoadBalancerServerId();
   }
   // Do 3 or 4 passes. Only 3 are necessary if the server_id is short enough
   // to fit in the first half of the connection ID (the decoder doesn't need
@@ -139,40 +122,16 @@
       absl::Span<uint8_t>(&right[2], server_id_len_ - half_len));
 }
 
-QuicConnectionId LoadBalancerConfig::Encrypt(
-    absl::Span<uint8_t> connection_id) const {
-  if (connection_id.length() < total_len()) {
+QuicConnectionId LoadBalancerConfig::FourPassEncrypt(
+    absl::Span<uint8_t> plaintext) const {
+  if (!key_.has_value()) {
     return QuicConnectionId();
   }
-  if (!key_.has_value()) {  // Plaintext connection ID
-    // Fill the nonce field with a hash of the Connection ID to avoid the nonce
-    // visibly increasing by one. This would allow observers to correlate
-    // connection IDs as being sequential and likely from the same connection,
-    // not just the same server.
-    absl::uint128 nonce_hash = QuicUtils::FNV1a_128_Hash(absl::string_view(
-        reinterpret_cast<char*>(connection_id.data()), connection_id.length()));
-    const uint64_t lo = absl::Uint128Low64(nonce_hash);
-    if (nonce_len_ <= sizeof(uint64_t)) {
-      memcpy(connection_id.data() + 1 + server_id_len_, &lo, nonce_len_);
-      return QuicConnectionId(connection_id);
-    }
-    memcpy(connection_id.data() + 1 + server_id_len_, &lo, sizeof(uint64_t));
-    const uint64_t hi = absl::Uint128High64(nonce_hash);
-    memcpy(connection_id.data() + 1 + server_id_len_ + sizeof(uint64_t), &hi,
-           nonce_len_ - sizeof(uint64_t));
-    return QuicConnectionId(connection_id);
-  }
-  if (plaintext_len() == kLoadBalancerBlockSize) {
-    AES_encrypt(connection_id.subspan(1, plaintext_len()).data(),
-                connection_id.data() + 1, &*key_);
-    return QuicConnectionId(connection_id);
-  }
-  // 4 Pass Encryption
   uint8_t left[kLoadBalancerBlockSize];
   uint8_t right[kLoadBalancerBlockSize];
   uint8_t half_len;  // half the length of the plaintext, rounded up
   bool is_length_odd =
-      InitializeFourPass(connection_id.data(), left, right, &half_len);
+      InitializeFourPass(plaintext.data() + 1, left, right, &half_len);
   for (uint8_t index = 1; index <= kNumLoadBalancerCryptoPasses; ++index) {
     EncryptionPass(index, half_len, is_length_odd, left, right);
   }
@@ -181,13 +140,34 @@
     // Combine the halves of the odd byte.
     left[half_len + 1] |= right[2];
   }
-  memcpy(connection_id.data() + 1, &left[2], half_len);
+  memcpy(plaintext.data() + 1, &left[2], half_len);
   if (is_length_odd) {
-    memcpy(connection_id.data() + 1 + half_len, &right[3], half_len - 1);
+    memcpy(plaintext.data() + 1 + half_len, &right[3], half_len - 1);
   } else {
-    memcpy(connection_id.data() + 1 + half_len, &right[2], half_len);
+    memcpy(plaintext.data() + 1 + half_len, &right[2], half_len);
   }
-  return QuicConnectionId(connection_id);
+  return QuicConnectionId(reinterpret_cast<char*>(plaintext.data()),
+                          total_len());
+}
+
+bool LoadBalancerConfig::BlockEncrypt(
+    const uint8_t plaintext[kLoadBalancerBlockSize],
+    uint8_t ciphertext[kLoadBalancerBlockSize]) const {
+  if (!key_.has_value()) {
+    return false;
+  }
+  AES_encrypt(plaintext, ciphertext, &*key_);
+  return true;
+}
+
+bool LoadBalancerConfig::BlockDecrypt(
+    const uint8_t ciphertext[kLoadBalancerBlockSize],
+    uint8_t plaintext[kLoadBalancerBlockSize]) const {
+  if (!block_decrypt_key_.has_value()) {
+    return false;
+  }
+  AES_decrypt(ciphertext, plaintext, &*block_decrypt_key_);
+  return true;
 }
 
 LoadBalancerConfig::LoadBalancerConfig(const uint8_t config_id,
@@ -220,11 +200,11 @@
   left[0] = plaintext_len();
   right[0] = plaintext_len();
   // Leave left_[1], right_[1] as zero. It will be set for each pass.
-  memcpy(&left[2], input + 1, *half_len);
+  memcpy(&left[2], input, *half_len);
   // If is_length_odd, then both left and right will have part of the middle
   // byte. Then that middle byte will be split in half via the bitmask in the
   // next step.
-  memcpy(&right[2], input + (plaintext_len() / 2) + 1, *half_len);
+  memcpy(&right[2], input + (plaintext_len() / 2), *half_len);
   if (is_length_odd) {
     left[*half_len + 1] &= 0xf0;
     right[2] &= 0x0f;
diff --git a/quiche/quic/load_balancer/load_balancer_config.h b/quiche/quic/load_balancer/load_balancer_config.h
index a3a5ca9..9ad396c 100644
--- a/quiche/quic/load_balancer/load_balancer_config.h
+++ b/quiche/quic/load_balancer/load_balancer_config.h
@@ -8,6 +8,7 @@
 #include <cstdint>
 #include <optional>
 
+#include "absl/base/attributes.h"
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "openssl/aes.h"
@@ -17,6 +18,10 @@
 
 namespace quic {
 
+namespace test {
+class LoadBalancerConfigPeer;
+}  // namespace test
+
 // The number of bits in the first byte used for the config ID
 inline constexpr uint8_t kConfigIdBits = 3;
 // The number of bits in the first byte used for the connection ID length, if
@@ -58,12 +63,24 @@
       uint8_t config_id, uint8_t server_id_len, uint8_t nonce_len);
 
   // Returns an invalid Server ID if ciphertext is too small, or needed keys are
-  // missing. |ciphertext| contains the full connection ID.
-  LoadBalancerServerId Decrypt(absl::Span<const uint8_t> ciphertext) const;
-  // Encrypts |connection_id|, which must be of the form first byte,
-  // server ID, nonce. Returns empty if plaintext is not long enough. The
-  // argument is NOT const, and will be overwritten.
-  QuicConnectionId Encrypt(absl::Span<uint8_t> connection_id) const;
+  // missing. |ciphertext| contains the full connection ID minus the first byte.
+  LoadBalancerServerId FourPassDecrypt(
+      absl::Span<const uint8_t> ciphertext) const;
+  // Returns an empty connection ID if the plaintext is too small, or needed
+  // keys are missing. |plaintext| contains the full unencrypted connection ID,
+  // including the first byte.
+  QuicConnectionId FourPassEncrypt(absl::Span<uint8_t> plaintext) const;
+
+  // Use the key to do a block encryption, which is used both in all cases of
+  // encrypted configs. Returns false if there's no key. Type char is
+  // convenient because that's what QuicConnectionId uses.
+  ABSL_MUST_USE_RESULT bool BlockEncrypt(
+      const uint8_t plaintext[kLoadBalancerBlockSize],
+      uint8_t ciphertext[kLoadBalancerBlockSize]) const;
+  // Returns false if the config does not require block decryption.
+  ABSL_MUST_USE_RESULT bool BlockDecrypt(
+      const uint8_t ciphertext[kLoadBalancerBlockSize],
+      uint8_t plaintext[kLoadBalancerBlockSize]) const;
 
   uint8_t config_id() const { return config_id_; }
   uint8_t server_id_len() const { return server_id_len_; }
@@ -75,6 +92,8 @@
   bool IsEncrypted() const { return key_.has_value(); }
 
  private:
+  friend class test::LoadBalancerConfigPeer;
+
   // Constructor is private because it doesn't validate input.
   LoadBalancerConfig(uint8_t config_id, uint8_t server_id_len,
                      uint8_t nonce_len, absl::string_view key);
diff --git a/quiche/quic/load_balancer/load_balancer_config_test.cc b/quiche/quic/load_balancer/load_balancer_config_test.cc
index 2c52b98..fd40412 100644
--- a/quiche/quic/load_balancer/load_balancer_config_test.cc
+++ b/quiche/quic/load_balancer/load_balancer_config_test.cc
@@ -4,6 +4,7 @@
 
 #include "quiche/quic/load_balancer/load_balancer_config.h"
 
+#include <array>
 #include <cstdint>
 #include <cstring>
 
@@ -18,6 +19,21 @@
 
 namespace test {
 
+class LoadBalancerConfigPeer {
+ public:
+  static bool InitializeFourPass(LoadBalancerConfig& config,
+                                 const uint8_t* input, uint8_t* left,
+                                 uint8_t* right, uint8_t* half_len) {
+    return config.InitializeFourPass(input, left, right, half_len);
+  }
+
+  static void EncryptionPass(LoadBalancerConfig& config, uint8_t index,
+                             uint8_t half_len, bool is_length_odd,
+                             uint8_t* left, uint8_t* right) {
+    config.EncryptionPass(index, half_len, is_length_odd, left, right);
+  }
+};
+
 namespace {
 
 constexpr char raw_key[] = {
@@ -87,69 +103,148 @@
   EXPECT_TRUE(config2->IsEncrypted());
 }
 
+// Compare EncryptionPass() results to the example in
+// draft-ietf-quic-load-balancers-15, Section 4.3.2.
+TEST_F(LoadBalancerConfigTest, TestEncryptionPassExample) {
+  auto config =
+      LoadBalancerConfig::Create(0, 3, 4, absl::string_view(raw_key, 16));
+  EXPECT_TRUE(config.has_value());
+  EXPECT_TRUE(config->IsEncrypted());
+  uint8_t input[] = {0x07, 0x31, 0x44, 0x1a, 0x9c, 0x69, 0xc2, 0x75};
+  std::array<uint8_t, kLoadBalancerBlockSize> left, right;
+  uint8_t half_len;
+
+  bool is_length_odd = LoadBalancerConfigPeer::InitializeFourPass(
+      *config, input + 1, left.data(), right.data(), &half_len);
+  EXPECT_TRUE(is_length_odd);
+  std::array<std::array<uint8_t, kLoadBalancerBlockSize>,
+             kNumLoadBalancerCryptoPasses + 1>
+      expected_left = {{
+          {0x07, 0x00, 0x31, 0x44, 0x1a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+          {0x07, 0x01, 0x31, 0x44, 0x1a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+          {0x07, 0x01, 0x02, 0x8e, 0x1b, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+          {0x07, 0x03, 0x02, 0x8e, 0x1b, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+          {0x07, 0x03, 0x8e, 0x9a, 0x91, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+      }};
+  std::array<std::array<uint8_t, kLoadBalancerBlockSize>,
+             kNumLoadBalancerCryptoPasses + 1>
+      expected_right = {{
+          {0x07, 0x00, 0x0c, 0x69, 0xc2, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+          {0x07, 0x00, 0x0f, 0x1a, 0x5b, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+          {0x07, 0x02, 0x0f, 0x1a, 0x5b, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+          {0x07, 0x02, 0x04, 0x94, 0x97, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+          {0x07, 0x04, 0x04, 0x94, 0x97, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00,
+           0x00, 0x00, 0x00, 0x00, 0x00},
+      }};
+
+  EXPECT_EQ(left, expected_left[0]);
+  EXPECT_EQ(right, expected_right[0]);
+  for (int i = 1; i <= kNumLoadBalancerCryptoPasses; ++i) {
+    LoadBalancerConfigPeer::EncryptionPass(*config, i, half_len, is_length_odd,
+                                           left.data(), right.data());
+    EXPECT_EQ(left, expected_left[i]);
+    EXPECT_EQ(right, expected_right[i]);
+  }
+}
+
+// Check that the encryption pass code can decode its own ciphertext. Various
+// pointer errors could cause the code to overwrite bits that contain
+// important information.
+TEST_F(LoadBalancerConfigTest, EncryptionPassesAreReversible) {
+  auto config =
+      LoadBalancerConfig::Create(0, 3, 4, absl::string_view(raw_key, 16));
+  std::array<uint8_t, kLoadBalancerBlockSize> start_left = {
+      0x07, 0x00, 0x31, 0x44, 0x1a, 0x90, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  std::array<uint8_t, kLoadBalancerBlockSize> start_right = {
+      0x07, 0x00, 0x0c, 0x69, 0xc2, 0x75, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  };
+  std::array<uint8_t, kLoadBalancerBlockSize> left = start_left,
+                                              right = start_right;
+  // Work left->right and right->left passes.
+  LoadBalancerConfigPeer::EncryptionPass(*config, 1, 4, true, left.data(),
+                                         right.data());
+  LoadBalancerConfigPeer::EncryptionPass(*config, 2, 4, true, left.data(),
+                                         right.data());
+  LoadBalancerConfigPeer::EncryptionPass(*config, 2, 4, true, left.data(),
+                                         right.data());
+  LoadBalancerConfigPeer::EncryptionPass(*config, 1, 4, true, left.data(),
+                                         right.data());
+  // Since index is manually written into the second byte only on input, it is
+  // not reversible.
+  left[1] = 0;
+  right[1] = 0;
+  EXPECT_EQ(left, start_left);
+  EXPECT_EQ(right, start_right);
+}
+
 // Tests for Encrypt() and Decrypt() are in LoadBalancerEncoderTest and
 // LoadBalancerDecoderTest, respectively.
 
-TEST_F(LoadBalancerConfigTest, ConfigIsCopyable) {
-  const uint8_t ptext[] = {0x00, 0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f,
+TEST_F(LoadBalancerConfigTest, InvalidBlockEncryption) {
+  uint8_t pt[kLoadBalancerBlockSize], ct[kLoadBalancerBlockSize];
+  auto pt_config = LoadBalancerConfig::CreateUnencrypted(0, 8, 8);
+  ASSERT_TRUE(pt_config.has_value());
+  EXPECT_FALSE(pt_config->BlockEncrypt(pt, ct));
+  EXPECT_FALSE(pt_config->BlockDecrypt(ct, pt));
+  EXPECT_TRUE(pt_config->FourPassEncrypt(absl::Span<uint8_t>(pt, sizeof(pt)))
+                  .IsEmpty());
+  EXPECT_FALSE(pt_config->FourPassDecrypt(absl::Span<uint8_t>(pt, sizeof(pt)))
+                   .IsValid());
+  auto small_cid_config =
+      LoadBalancerConfig::Create(0, 3, 4, absl::string_view(raw_key, 16));
+  ASSERT_TRUE(small_cid_config.has_value());
+  EXPECT_TRUE(small_cid_config->BlockEncrypt(pt, ct));
+  EXPECT_FALSE(small_cid_config->BlockDecrypt(ct, pt));
+  auto block_config =
+      LoadBalancerConfig::Create(0, 8, 8, absl::string_view(raw_key, 16));
+  ASSERT_TRUE(block_config.has_value());
+  EXPECT_TRUE(block_config->BlockEncrypt(pt, ct));
+  EXPECT_TRUE(block_config->BlockDecrypt(ct, pt));
+}
+
+// Block decrypt test from the Test Vector in
+// draft-ietf-quic-load-balancers-15, Appendix B.
+TEST_F(LoadBalancerConfigTest, BlockEncryptionExample) {
+  const uint8_t ptext[] = {0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f,
                            0xee, 0x08, 0x0d, 0xbf, 0x48, 0xc0, 0xd1, 0xe5};
-  uint8_t ctext[] = {0x00, 0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2,
-                     0xb9, 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3};
+  const uint8_t ctext[] = {0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2,
+                           0xb9, 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3};
   const char key[] = {0x8f, 0x95, 0xf0, 0x92, 0x45, 0x76, 0x5f, 0x80,
                       0x25, 0x69, 0x34, 0xe5, 0x0c, 0x66, 0x20, 0x7f};
+  uint8_t result[sizeof(ptext)];
   auto config = LoadBalancerConfig::Create(0, 8, 8, absl::string_view(key, 16));
-  ASSERT_TRUE(config.has_value());
+  EXPECT_TRUE(config->BlockEncrypt(ptext, result));
+  EXPECT_EQ(memcmp(result, ctext, sizeof(ctext)), 0);
+  EXPECT_TRUE(config->BlockDecrypt(ctext, result));
+  EXPECT_EQ(memcmp(result, ptext, sizeof(ptext)), 0);
+}
+
+TEST_F(LoadBalancerConfigTest, ConfigIsCopyable) {
+  const uint8_t ptext[] = {0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f,
+                           0xee, 0x08, 0x0d, 0xbf, 0x48, 0xc0, 0xd1, 0xe5};
+  const uint8_t ctext[] = {0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2,
+                           0xb9, 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3};
+  const char key[] = {0x8f, 0x95, 0xf0, 0x92, 0x45, 0x76, 0x5f, 0x80,
+                      0x25, 0x69, 0x34, 0xe5, 0x0c, 0x66, 0x20, 0x7f};
+  uint8_t result[sizeof(ptext)];
+  auto config = LoadBalancerConfig::Create(0, 8, 8, absl::string_view(key, 16));
   auto config2 = config;
-  ASSERT_TRUE(config2.has_value());
-  uint8_t temp_ptext[sizeof(ptext)];  // the input will be overwritten, so copy
-  memcpy(temp_ptext, ptext, sizeof(ptext));
-  QuicConnectionId cid1 =
-      config->Encrypt(absl::Span<uint8_t>(temp_ptext, sizeof(ptext)));
-  EXPECT_EQ(cid1.length(), sizeof(ctext));
-  EXPECT_EQ(memcmp(cid1.data(), ctext, sizeof(ctext)), 0);
-  memcpy(temp_ptext, ptext, sizeof(ptext));
-  QuicConnectionId cid2 =
-      config2->Encrypt(absl::Span<uint8_t>(temp_ptext, sizeof(ptext)));
-  EXPECT_EQ(cid2.length(), sizeof(ctext));
-  EXPECT_EQ(memcmp(cid2.data(), ctext, sizeof(ctext)), 0);
-}
-
-TEST_F(LoadBalancerConfigTest, OnePassEncryptAndDecryptIgnoreAdditionalBytes) {
-  uint8_t ptext[] = {0x00, 0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f, 0xee,
-                     0x08, 0x0d, 0xbf, 0x48, 0xc0, 0xd1, 0xe5, 0xda, 0x41};
-  uint8_t ctext[] = {0x00, 0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2, 0xb9,
-                     0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3, 0xda, 0x41};
-  const char key[] = {0x8f, 0x95, 0xf0, 0x92, 0x45, 0x76, 0x5f, 0x80,
-                      0x25, 0x69, 0x34, 0xe5, 0x0c, 0x66, 0x20, 0x7f};
-  auto config = LoadBalancerConfig::Create(0, 8, 8, absl::string_view(key, 16));
-  ASSERT_TRUE(config.has_value());
-  LoadBalancerServerId original_server_id(absl::Span<uint8_t>(&ptext[1], 8));
-  QuicConnectionId cid =
-      config->Encrypt(absl::Span<uint8_t>(ptext, sizeof(ptext)));
-  EXPECT_EQ(cid.length(), sizeof(ctext));
-  EXPECT_EQ(memcmp(cid.data(), ctext, sizeof(ctext)), 0);
-  LoadBalancerServerId server_id = config->Decrypt(absl::Span<const uint8_t>(
-      reinterpret_cast<const uint8_t *>(cid.data()), cid.length()));
-  EXPECT_EQ(server_id, original_server_id);
-}
-
-TEST_F(LoadBalancerConfigTest, FourPassEncryptAndDecryptIgnoreAdditionalBytes) {
-  uint8_t ptext[] = {0x00, 0xed, 0x79, 0x3a, 0xee,
-                     0x08, 0x0d, 0xbf, 0xda, 0x41};
-  uint8_t ctext[] = {0x00, 0x41, 0x26, 0xee, 0x38,
-                     0xbf, 0x54, 0x54, 0xda, 0x41};
-  const char key[] = {0x8f, 0x95, 0xf0, 0x92, 0x45, 0x76, 0x5f, 0x80,
-                      0x25, 0x69, 0x34, 0xe5, 0x0c, 0x66, 0x20, 0x7f};
-  auto config = LoadBalancerConfig::Create(0, 3, 4, absl::string_view(key, 16));
-  ASSERT_TRUE(config.has_value());
-  LoadBalancerServerId original_server_id(absl::Span<uint8_t>(&ptext[1], 3));
-  QuicConnectionId cid =
-      config->Encrypt(absl::Span<uint8_t>(ptext, sizeof(ptext)));
-  EXPECT_EQ(cid.length(), sizeof(ctext));
-  EXPECT_EQ(memcmp(cid.data(), ctext, sizeof(ctext)), 0);
-  LoadBalancerServerId server_id = config->Decrypt(absl::Span<const uint8_t>(
-      reinterpret_cast<const uint8_t *>(cid.data()), cid.length()));
-  EXPECT_EQ(server_id, original_server_id);
+  EXPECT_TRUE(config->BlockEncrypt(ptext, result));
+  EXPECT_EQ(memcmp(result, ctext, sizeof(ctext)), 0);
+  EXPECT_TRUE(config2->BlockEncrypt(ptext, result));
+  EXPECT_EQ(memcmp(result, ctext, sizeof(ctext)), 0);
 }
 
 }  // namespace
diff --git a/quiche/quic/load_balancer/load_balancer_decoder.cc b/quiche/quic/load_balancer/load_balancer_decoder.cc
index 8a564f1..0bbb804 100644
--- a/quiche/quic/load_balancer/load_balancer_decoder.cc
+++ b/quiche/quic/load_balancer/load_balancer_decoder.cc
@@ -5,6 +5,7 @@
 #include "quiche/quic/load_balancer/load_balancer_decoder.h"
 
 #include <cstdint>
+#include <cstring>
 #include <optional>
 
 #include "absl/types/span.h"
@@ -45,9 +46,26 @@
   if (!config.has_value()) {
     return LoadBalancerServerId();
   }
-  return config->Decrypt(absl::MakeConstSpan(
-      reinterpret_cast<const uint8_t*>(connection_id.data()),
-      connection_id.length()));
+  // Benchmark tests show that minimizing the computation inside
+  // LoadBalancerConfig saves CPU cycles.
+  if (connection_id.length() < config->total_len()) {
+    return LoadBalancerServerId();
+  }
+  const uint8_t* data =
+      reinterpret_cast<const uint8_t*>(connection_id.data()) + 1;
+  uint8_t server_id_len = config->server_id_len();
+  if (!config->IsEncrypted()) {
+    return LoadBalancerServerId(absl::Span<const uint8_t>(data, server_id_len));
+  }
+  if (config->plaintext_len() == kLoadBalancerBlockSize) {
+    uint8_t scratch[kLoadBalancerBlockSize];
+    if (!config->BlockDecrypt(data, scratch)) {
+      return LoadBalancerServerId();
+    }
+    return LoadBalancerServerId(absl::Span<uint8_t>(scratch, server_id_len));
+  }
+  return config->FourPassDecrypt(
+      absl::MakeConstSpan(data, connection_id.length() - 1));
 }
 
 std::optional<uint8_t> LoadBalancerDecoder::GetConfigId(
diff --git a/quiche/quic/load_balancer/load_balancer_decoder_test.cc b/quiche/quic/load_balancer/load_balancer_decoder_test.cc
index 2f83672..cc86d25 100644
--- a/quiche/quic/load_balancer/load_balancer_decoder_test.cc
+++ b/quiche/quic/load_balancer/load_balancer_decoder_test.cc
@@ -240,6 +240,20 @@
   EXPECT_FALSE(config->IsEncrypted());
 }
 
+TEST_F(LoadBalancerDecoderTest, OnePassIgnoreAdditionalBytes) {
+  uint8_t ptext[] = {0x00, 0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f, 0xee,
+                     0x08, 0x0d, 0xbf, 0x48, 0xc0, 0xd1, 0xe5, 0xda, 0x41};
+  uint8_t ctext[] = {0x00, 0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2, 0xb9,
+                     0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3, 0xda, 0x41};
+  LoadBalancerDecoder decoder;
+  decoder.AddConfig(
+      *LoadBalancerConfig::Create(0, 8, 8, absl::string_view(kRawKey, 16)));
+  LoadBalancerServerId original_server_id(absl::Span<uint8_t>(&ptext[1], 8));
+  QuicConnectionId cid(absl::Span<uint8_t>(ctext, sizeof(ctext)));
+  LoadBalancerServerId server_id = decoder.GetServerId(cid);
+  EXPECT_EQ(server_id, original_server_id);
+}
+
 }  // namespace
 
 }  // namespace test
diff --git a/quiche/quic/load_balancer/load_balancer_encoder.cc b/quiche/quic/load_balancer/load_balancer_encoder.cc
index 0f72da3..01d63d5 100644
--- a/quiche/quic/load_balancer/load_balancer_encoder.cc
+++ b/quiche/quic/load_balancer/load_balancer_encoder.cc
@@ -5,14 +5,17 @@
 #include "quiche/quic/load_balancer/load_balancer_encoder.h"
 
 #include <cstdint>
+#include <cstring>
 #include <optional>
 
 #include "absl/cleanup/cleanup.h"
 #include "absl/numeric/int128.h"
+#include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "quiche/quic/core/crypto/quic_random.h"
 #include "quiche/quic/core/quic_connection_id.h"
 #include "quiche/quic/core/quic_data_writer.h"
+#include "quiche/quic/core/quic_utils.h"
 #include "quiche/quic/core/quic_versions.h"
 #include "quiche/quic/load_balancer/load_balancer_config.h"
 #include "quiche/quic/load_balancer/load_balancer_server_id.h"
@@ -140,7 +143,35 @@
   if (!WriteUint128(next_nonce, config_->nonce_len(), writer)) {
     return QuicConnectionId();
   }
-  return config_->Encrypt(absl::Span<uint8_t>(result, config_->total_len()));
+  if (!config_->IsEncrypted()) {
+    // Fill the nonce field with a hash of the Connection ID to avoid the nonce
+    // visibly increasing by one. This would allow observers to correlate
+    // connection IDs as being sequential and likely from the same connection,
+    // not just the same server.
+    absl::uint128 nonce_hash = QuicUtils::FNV1a_128_Hash(absl::string_view(
+        reinterpret_cast<char *>(result), config_->total_len()));
+    const uint64_t lo = absl::Uint128Low64(nonce_hash);
+    if (config_->nonce_len() <= sizeof(uint64_t)) {
+      memcpy(&result[1 + config_->server_id_len()], &lo, config_->nonce_len());
+      return QuicConnectionId(reinterpret_cast<char *>(result),
+                              config_->total_len());
+    }
+    memcpy(&result[1 + config_->server_id_len()], &lo, sizeof(uint64_t));
+    const uint64_t hi = absl::Uint128High64(nonce_hash);
+    memcpy(&result[1 + config_->server_id_len() + sizeof(uint64_t)], &hi,
+           config_->nonce_len() - sizeof(uint64_t));
+    return QuicConnectionId(reinterpret_cast<char *>(result),
+                            config_->total_len());
+  }
+  if (config_->plaintext_len() == kLoadBalancerBlockSize) {
+    if (!config_->BlockEncrypt(&result[1], &result[1])) {
+      return QuicConnectionId();
+    }
+    return (QuicConnectionId(reinterpret_cast<char *>(result),
+                             config_->total_len()));
+  }
+  return config_->FourPassEncrypt(
+      absl::Span<uint8_t>(result, config_->total_len()));
 }
 
 std::optional<QuicConnectionId> LoadBalancerEncoder::GenerateNextConnectionId(