RFR: 8324648: Avoid NoSuchMethodError when instantiating NativePRNG

Oli Gillespie ogillespie at openjdk.org
Thu Feb 1 11:26:01 UTC 2024


On Wed, 24 Jan 2024 15:42:05 GMT, Oli Gillespie <ogillespie at openjdk.org> wrote:

> A typical call to `new SecureRandom()` is slowed down by looking for a constructor in NativePRNG which takes `java.security.SecureRandomParameters`. NativePRNG does not have such a constructor, so the search fails [here](https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/security/Provider.java#L1951-L1957), incurring all the cost of the lookup and creating a subsequent exception.
> 
> Creating a dummy constructor which takes and ignores this parameter will speed up `new SecureRandom()` calls significantly. 
> 
> The benchmark from https://github.com/openjdk/jdk/pull/17559 shows around 80% reduction in time taken to create a new SecureRandom with NativePRNG (default on my machine).
> 
> 
> Before
> SecureRandomBench.newSecureRandom  avgt  2930 ± 50 ns/op
> 
> After
> SecureRandomBench.newSecureRandom  avgt  510 ± 16 ns/op

The more I think about it, adding a new public constructor seems a bit ugly and potentially confusing.

So I'll offer a few more ideas. Which do you think is best?

1. New public constructor with IllegalArgumentException if params != null


// constructor, unused argument to speed up lookups from Provider
public NativePRNG(SecureRandomParameters params) {
    this();
    if (params != null) {
        throw new IllegalArgumentException("Unsupported params: " + params.getClass());
    }
}


2. Use `getConstructors()` instead of `getConstructor(...)`, and manually look for the match (basically implement our own `tryGetConstructor`). This way, we avoid the exception which is the main cost of the current approach, but don't need to add any special cases. Benchmark says this is only a little slower than 1. (550ns vs 510ns).


// new style with params
for (Constructor<?> c : getImplClass().getConstructors()) {
    Class<?>[] args = c.getParameterTypes();
    if (args.length == 1 && args[0].equals(ctrParamClz)) {
        // found match
        return c.newInstance(ctorParamObj);
    }
}
// old style without params
if (ctorParamObj == null) {
    return newInstanceOf();
} else {
    throw new NoSuchMethodException("...");
}


3. Add explicit handling of NativePRNG inside Provider.


static HashSet<String> NO_ARG_SECURE_RANDOMS = new HashSet<>();
static {
    NO_ARG_SECURE_RANDOMS.add("sun.security.provider.NativePRNG");
    NO_ARG_SECURE_RANDOMS.add("sun.security.provider.NativePRNG$Blocking");
    NO_ARG_SECURE_RANDOMS.add("sun.security.provider.NativePRNG$NonBlocking");
};

private Object newInstanceUtil(Class<?> ctrParamClz, Object ctorParamObj)
        throws Exception {
    if (ctrParamClz == null) {
        return newInstanceOf();
    } else if (ctorParamObj == null && NO_ARG_SECURE_RANDOMS.contains(getImplClass().getName())) {
        return newInstanceOf();
    } else {


4. Add some kind of caching in Provider to only pay the cost once (not coded up yet, potentially a bit annoying to also save the NSME if we want to keep that behaviour).

-------------

PR Comment: https://git.openjdk.org/jdk/pull/17560#issuecomment-1921098319



More information about the security-dev mailing list