From 39c3bc1958b71077e47373b629d90c04df20a324 Mon Sep 17 00:00:00 2001 From: Samin Rahman Date: Mon, 12 Jan 2026 13:51:16 +1100 Subject: [PATCH 1/2] Cached Cipher instances in AES; Optimized and cached oldest key by site id in RotatingClientKeyProvider --- .../com/uid2/shared/encryption/AesGcm.java | 28 ++++++++++++----- .../reader/RotatingClientKeyProvider.java | 30 +++++++++++++++---- 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/uid2/shared/encryption/AesGcm.java b/src/main/java/com/uid2/shared/encryption/AesGcm.java index 1dda8d8c..5bb40294 100644 --- a/src/main/java/com/uid2/shared/encryption/AesGcm.java +++ b/src/main/java/com/uid2/shared/encryption/AesGcm.java @@ -4,19 +4,27 @@ import com.uid2.shared.model.EncryptionKey; import com.uid2.shared.model.KeyIdentifier; import com.uid2.shared.model.KeysetKey; -import io.vertx.core.buffer.Buffer; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; public class AesGcm { - private static final String cipherScheme = "AES/GCM/NoPadding"; + private static final String CIPHER_SCHEME = "AES/GCM/NoPadding"; public static final int GCM_AUTHTAG_LENGTH = 16; public static final int GCM_IV_LENGTH = 12; + private static final ThreadLocal CIPHER = ThreadLocal.withInitial(() -> { + try { + return Cipher.getInstance(CIPHER_SCHEME); + } catch (GeneralSecurityException e) { + throw new RuntimeException("Unable to create cipher", e); + } + }); + public static EncryptedPayload encrypt(byte[] b, KeysetKey key) { return encrypt(b, key.getKeyBytes(), key.getKeyIdentifier()); } @@ -32,11 +40,16 @@ private static EncryptedPayload encrypt(byte[] b, byte[] secretBytes, KeyIdentif public static byte[] encrypt(byte[] b, byte[] secretBytes) { try { final SecretKey k = new SecretKeySpec(secretBytes, "AES"); - final Cipher c = Cipher.getInstance(cipherScheme); + final Cipher c = CIPHER.get(); final byte[] ivBytes = Random.getBytes(GCM_IV_LENGTH); - GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_AUTHTAG_LENGTH * 8, ivBytes); + final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_AUTHTAG_LENGTH * 8, ivBytes); c.init(Cipher.ENCRYPT_MODE, k, gcmParameterSpec); - return Buffer.buffer().appendBytes(ivBytes).appendBytes(c.doFinal(b)).getBytes(); + + // Pre-allocate output: IV + ciphertext + auth tag + final byte[] result = new byte[GCM_IV_LENGTH + c.getOutputSize(b.length)]; + System.arraycopy(ivBytes, 0, result, 0, GCM_IV_LENGTH); + c.doFinal(b, 0, b.length, result, GCM_IV_LENGTH); + return result; } catch (Exception e) { throw new RuntimeException("Unable to Encrypt", e); } @@ -50,9 +63,10 @@ public static byte[] decrypt(byte[] encryptedBytes, int offset, byte[] secretByt try { final SecretKey key = new SecretKeySpec(secretBytes, "AES"); final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_AUTHTAG_LENGTH * 8, encryptedBytes, offset, GCM_IV_LENGTH); - final Cipher c = Cipher.getInstance(cipherScheme); + final Cipher c = CIPHER.get(); c.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec); - return c.doFinal(encryptedBytes, offset + GCM_IV_LENGTH, encryptedBytes.length - offset - GCM_IV_LENGTH); + final int dataOffset = offset + GCM_IV_LENGTH; + return c.doFinal(encryptedBytes, dataOffset, encryptedBytes.length - dataOffset); } catch (Exception e) { throw new RuntimeException("Unable to Decrypt", e); } diff --git a/src/main/java/com/uid2/shared/store/reader/RotatingClientKeyProvider.java b/src/main/java/com/uid2/shared/store/reader/RotatingClientKeyProvider.java index c50b4bdb..493a19f5 100644 --- a/src/main/java/com/uid2/shared/store/reader/RotatingClientKeyProvider.java +++ b/src/main/java/com/uid2/shared/store/reader/RotatingClientKeyProvider.java @@ -14,6 +14,8 @@ import java.util.Collection; import java.util.Comparator; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; /* 1. metadata.json format @@ -42,6 +44,10 @@ public class RotatingClientKeyProvider implements IClientKeyProvider, StoreReader> { private final ScopedStoreReader> reader; private final AuthorizableStore authorizableStore; + private final ConcurrentHashMap oldestClientKeyBySiteIdCache = new ConcurrentHashMap<>(); + private volatile long snapshotVersion = 0; + + private record VersionedValue(long version, Optional value) {} public RotatingClientKeyProvider(DownloadCloudStorage fileStreamProvider, StoreScope scope) { this.reader = new ScopedStoreReader<>(fileStreamProvider, scope, new ClientParser(), "auth keys"); @@ -64,9 +70,13 @@ public long getVersion(JsonObject metadata) { } @Override - public long loadContent(JsonObject metadata) throws Exception { + public long loadContent(JsonObject metadata) throws Exception { long version = reader.loadContent(metadata, "client_keys"); authorizableStore.refresh(getAll()); + + // Versioning to prevent race conditions when reading the oldest client key + oldestClientKeyBySiteIdCache.clear(); + snapshotVersion = getVersion(metadata); return version; } @@ -102,10 +112,18 @@ public IAuthorizable get(String key) { @Override public ClientKey getOldestClientKey(int siteId) { - return this.reader.getSnapshot().stream() - .filter(k -> k.getSiteId() == siteId) // filter by site id - .sorted(Comparator.comparing(ClientKey::getCreated)) // sort by key creation timestamp ascending - .findFirst() // return the oldest key - .orElse(null); + long currentVersion = snapshotVersion; + VersionedValue cached = oldestClientKeyBySiteIdCache.get(siteId); + + if (cached != null && cached.version() == currentVersion) { + return cached.value().orElse(null); + } + + Optional computed = this.reader.getSnapshot().stream() + .filter(k -> k.getSiteId() == siteId) + .min(Comparator.comparingLong(ClientKey::getCreated)); + + oldestClientKeyBySiteIdCache.put(siteId, new VersionedValue(currentVersion, computed)); + return computed.orElse(null); } } From 4f7256d7b24fac6d9b4b8ad8a606f95b65376036 Mon Sep 17 00:00:00 2001 From: Release Workflow Date: Mon, 12 Jan 2026 03:52:16 +0000 Subject: [PATCH 2/2] [CI Pipeline] Released Snapshot version: 11.3.4-alpha-335-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0eb36c82..66de8748 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.uid2 uid2-shared - 11.3.3 + 11.3.4-alpha-335-SNAPSHOT ${project.groupId}:${project.artifactId} Library for all the shared uid2 operations https://github.com/IABTechLab/uid2docs