diff --git a/JenkinsfilePerformance b/JenkinsfilePerformance index 0554ecab3..cd6f8c511 100644 --- a/JenkinsfilePerformance +++ b/JenkinsfilePerformance @@ -387,6 +387,7 @@ pipeline { 'ibm.jceplus.jmh.PBEBenchmark', \ 'ibm.jceplus.jmh.PBKDF2Benchmark', \ 'ibm.jceplus.jmh.RandomBenchmark', \ + 'ibm.jceplus.jmh.RandomNewInstanceBenchmark', \ 'ibm.jceplus.jmh.RSACipherBenchmark', \ 'ibm.jceplus.jmh.RSAKeyGeneratorBenchmark', \ 'ibm.jceplus.jmh.RSASignatureBenchmark', \ diff --git a/src/main/java/com/ibm/crypto/plus/provider/base/ExtendedRandom.java b/src/main/java/com/ibm/crypto/plus/provider/base/ExtendedRandom.java index 1d9821abd..db1380097 100644 --- a/src/main/java/com/ibm/crypto/plus/provider/base/ExtendedRandom.java +++ b/src/main/java/com/ibm/crypto/plus/provider/base/ExtendedRandom.java @@ -16,7 +16,13 @@ public final class ExtendedRandom { private OpenJCEPlusProvider provider; private NativeInterface nativeInterface; - final long ockPRNGContextId; + private final String algName; + + long ockPRNGContextId; + private boolean usingSharedContext = true; + + private static final ThreadLocal prngContextBufferSha256 = new ThreadLocal(); + private static final ThreadLocal prngContextBufferSha512 = new ThreadLocal(); public static ExtendedRandom getInstance(String algName, OpenJCEPlusProvider provider) throws OCKException { @@ -32,11 +38,35 @@ public static ExtendedRandom getInstance(String algName, OpenJCEPlusProvider pro } private ExtendedRandom(String algName, OpenJCEPlusProvider provider) throws OCKException { + this.algName = algName; this.provider = provider; this.nativeInterface = provider.isFIPS() ? NativeOCKAdapterFIPS.getInstance() : NativeOCKAdapterNonFIPS.getInstance(); - this.ockPRNGContextId = this.nativeInterface.EXTRAND_create(algName); + this.ockPRNGContextId = getPRNGContext(algName); + } + + private long getPRNGContext(String algName) throws OCKException { + PRNGContextPointer prngCtx = null; + ThreadLocal prngCtxBuffer = null; + + switch (algName) { + case "SHA256": + prngCtxBuffer = prngContextBufferSha256; + break; + case "SHA512": + prngCtxBuffer = prngContextBufferSha512; + break; + default: + throw new IllegalArgumentException( + "Unsupported HASHDRBG algorithm: " + algName); + } - this.provider.registerCleanable(this, cleanOCKResources(ockPRNGContextId, nativeInterface)); + prngCtx = prngCtxBuffer.get(); + if (prngCtx == null) { + prngCtx = new PRNGContextPointer(algName, this.nativeInterface, this.provider); + prngCtxBuffer.set(prngCtx); + } + + return prngCtx.getCtx(); } public synchronized void nextBytes(byte[] bytes) throws OCKException { @@ -55,11 +85,24 @@ public synchronized void setSeed(byte[] seed) throws OCKException { } if (seed.length > 0) { + createNonSharedContextForReSeed(); this.nativeInterface.EXTRAND_setSeed(ockPRNGContextId, seed); } } - private Runnable cleanOCKResources(long ockPRNGContextId, NativeInterface nativeInterface) { + private void createNonSharedContextForReSeed() throws OCKException { + if (!usingSharedContext) { + return; + } + + long privateCtx = this.nativeInterface.EXTRAND_create(algName); + this.ockPRNGContextId = privateCtx; + this.usingSharedContext = false; + + this.provider.registerCleanable(this, cleanNonSharedOCKResources(privateCtx, nativeInterface)); + } + + private static Runnable cleanNonSharedOCKResources(long ockPRNGContextId, NativeInterface nativeInterface) { return () -> { try { if (ockPRNGContextId != 0) { @@ -67,10 +110,38 @@ private Runnable cleanOCKResources(long ockPRNGContextId, NativeInterface native } } catch (Exception e) { if (OpenJCEPlusProvider.getDebug() != null) { - OpenJCEPlusProvider.getDebug().println("An error occurred while cleaning : " + e.getMessage()); + OpenJCEPlusProvider.getDebug().println("An error occurred while cleaning: " + e.getMessage()); e.printStackTrace(); } } }; } + + private static final class PRNGContextPointer { + final long prngCtx; + + PRNGContextPointer(String algName, NativeInterface nativeInterface, OpenJCEPlusProvider provider) throws OCKException { + this.prngCtx = nativeInterface.EXTRAND_create(algName); + provider.registerCleanable(this, cleanOCKResources(this.prngCtx, nativeInterface)); + } + + long getCtx() { + return this.prngCtx; + } + + private static Runnable cleanOCKResources(long ockPRNGContextId, NativeInterface nativeInterface) { + return () -> { + try { + if (ockPRNGContextId != 0) { + nativeInterface.EXTRAND_delete(ockPRNGContextId); + } + } catch (Exception e) { + if (OpenJCEPlusProvider.getDebug() != null) { + OpenJCEPlusProvider.getDebug().println("An error occurred while cleaning : " + e.getMessage()); + e.printStackTrace(); + } + } + }; + } + } } diff --git a/src/test/java/ibm/jceplus/jmh/RandomNewInstanceBenchmark.java b/src/test/java/ibm/jceplus/jmh/RandomNewInstanceBenchmark.java new file mode 100644 index 000000000..22970e380 --- /dev/null +++ b/src/test/java/ibm/jceplus/jmh/RandomNewInstanceBenchmark.java @@ -0,0 +1,67 @@ +/* + * Copyright IBM Corp. 2026, 2026 + * + * This code is free software; you can redistribute it and/or modify it + * under the terms provided by IBM in the LICENSE file that accompanied + * this code, including the "Classpath" Exception described therein. + */ + +package ibm.jceplus.jmh; + +import java.security.SecureRandom; +import java.util.concurrent.TimeUnit; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; + +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.SECONDS) +@State(Scope.Benchmark) +@Warmup(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 4, time = 30, timeUnit = TimeUnit.SECONDS) +public class RandomNewInstanceBenchmark extends JMHBase { + + @Param({"16", "2048", "32768"}) + private int payloadSize; + + private byte[] payload; + + private String algorithm; + private String provider; + + @Param({"SHA256DRBG|OpenJCEPlus", "SHA512DRBG|OpenJCEPlus", "SHA1PRNG|SUN", "DRBG|SUN"}) + private String randomToTest; + + @Setup + public void setup() throws Exception { + String[] algAndProvider = randomToTest.split("\\|"); + algorithm = algAndProvider[0]; + provider = algAndProvider[1]; + super.setup(provider); + payload = new byte[payloadSize]; + } + + @Benchmark + public byte[] runSecureRandom() throws Exception { + SecureRandom random = SecureRandom.getInstance(algorithm, provider); + random.nextBytes(payload); + return payload; + } + + public static void main(String[] args) throws RunnerException { + String testSimpleName = RandomNewInstanceBenchmark.class.getSimpleName(); + Options opt = optionsBuild(testSimpleName, testSimpleName); + + new Runner(opt).run(); + } +}