Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions JenkinsfilePerformance
Original file line number Diff line number Diff line change
Expand Up @@ -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', \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ public final class ExtendedRandom {

private OpenJCEPlusProvider provider;
private NativeInterface nativeInterface;
final long ockPRNGContextId;
private final String algName;

private long ockPRNGContextId;
private boolean usingThreadLocalContext = true;

private static final ThreadLocal<PRNGContextPointer> prngContextBufferSha256 = new ThreadLocal<PRNGContextPointer>();
Comment thread
jasonkatonica marked this conversation as resolved.
private static final ThreadLocal<PRNGContextPointer> prngContextBufferSha512 = new ThreadLocal<PRNGContextPointer>();

public static ExtendedRandom getInstance(String algName, OpenJCEPlusProvider provider)
throws NativeException {
Expand All @@ -32,11 +38,35 @@ public static ExtendedRandom getInstance(String algName, OpenJCEPlusProvider pro
}

private ExtendedRandom(String algName, OpenJCEPlusProvider provider) throws NativeException {
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 NativeException {
PRNGContextPointer prngCtx = null;
ThreadLocal<PRNGContextPointer> prngCtxBuffer = null;

switch (algName) {
case "SHA256":
prngCtxBuffer = prngContextBufferSha256;
break;
case "SHA512":
prngCtxBuffer = prngContextBufferSha512;
break;
default:
throw new IllegalArgumentException(
"Unsupported HASHDRBG algorithm: " + algName);
}

prngCtx = prngCtxBuffer.get();
if (prngCtx == null) {
prngCtx = new PRNGContextPointer(algName, this.nativeInterface, this.provider);
prngCtxBuffer.set(prngCtx);
}

this.provider.registerCleanable(this, cleanOCKResources(ockPRNGContextId, nativeInterface));
return prngCtx.getCtx();
}

public synchronized void nextBytes(byte[] bytes) throws NativeException {
Expand All @@ -55,22 +85,48 @@ public synchronized void setSeed(byte[] seed) throws NativeException {
}

if (seed.length > 0) {
createInstanceContextForReSeed();
this.nativeInterface.EXTRAND_setSeed(ockPRNGContextId, seed);
}
}

private Runnable cleanOCKResources(long ockPRNGContextId, NativeInterface nativeInterface) {
private void createInstanceContextForReSeed() throws NativeException {
if (!usingThreadLocalContext) {
return;
}

long instanceCtx = this.nativeInterface.EXTRAND_create(algName);
this.ockPRNGContextId = instanceCtx;
this.usingThreadLocalContext = false;

this.provider.registerCleanable(this, cleanOCKResources(instanceCtx, nativeInterface));
}

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());
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 NativeException {
this.prngCtx = nativeInterface.EXTRAND_create(algName);
provider.registerCleanable(this, ExtendedRandom.cleanOCKResources(this.prngCtx, nativeInterface));
}

long getCtx() {
return this.prngCtx;
}
}
}
67 changes: 67 additions & 0 deletions src/test/java/ibm/jceplus/jmh/RandomNewInstanceBenchmark.java
Original file line number Diff line number Diff line change
@@ -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 {
Comment thread
jasonkatonica marked this conversation as resolved.

@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();
}
}
Loading