1

I have to create a lot of signatures with the below parameters, and with the following code, it takes around 10 msec to create a signature with an Intel Core i5-6500 CPU.

Is it normal for creating signatures with these parameters to take around 10 ms on a regular PC? How could I improve this time? I would be very happy if I could achieve 1ms per signature.

    Security.addProvider(new BouncyCastleProvider());

    KeyPairGenerator g = KeyPairGenerator.getInstance("ECDSA", "BC");
    g.initialize(ECNamedCurveTable.getParameterSpec("P-256"));
    KeyPair keyPair = g.generateKeyPair();

    Signature s = Signature.getInstance("SHA256withECDSA");
    s.initSign(keyPair.getPrivate());

    s.update("first".getBytes());
    long start = System.currentTimeMillis();
    s.sign(); // Takes about 30 msecs
    System.out.println(System.currentTimeMillis() - start);

    s.update("second".getBytes());
    start = System.currentTimeMillis();
    s.sign(); // Takes about 10 msecs for this and every further signature
    System.out.println(System.currentTimeMillis() - start);

I have tried the suggestions from here, but I could not achieve a performance increase.

Albert P
  • 365
  • 1
  • 14
  • Well, you would expect the cryptographic signature operation to be computationally expensive. Whether 10ms is fast or slow is a little bit hard to judge. It would be interesting to compare that number to other implementations both in Java and other languages. – Robby Cornelissen May 11 '18 at 10:10
  • 1
    To add on the comment about the expense for crypto functions, you also have to know that most of these are designed "timeless". It means that they don't save time by skipping unnecessary checks or actions, so as to take the same amount of time in all conditions, to prevent information gathering based on their timing. – Kaddath May 11 '18 at 10:24
  • 1
    You are performing key generation and signing. Please benchmark signing alone. Also, please run a loop and generate 10,000 or 100,000 signatures and supply an average time for the operation. Also see [How can I benchmark signature algorithms (HMAC vs RSA) and compare them well?](https://stackoverflow.com/q/20070946/608639), [Benchmarking symmetric and asymmetric cryptography](https://stackoverflow.com/q/7568777/608639), [How to benchmark a single algorithm in SUPERCOP?](https://stackoverflow.com/q/43158438/608639), etc. – jww May 11 '18 at 11:53
  • 1
    One optimization: create a single `SecureRandom` instance and pass it to every `Signature.initSign()` call. – President James K. Polk May 11 '18 at 11:56
  • The 10ms comes from measuring the execution time of `Signature.sign()`, as my code snippet shows. It is true, however, that I did not measure it a 10,000 times. – Albert P May 11 '18 at 12:03
  • Thanks @MaartenBodewes, I checked out the provider of the Signature and it was SunEC. However, when I explicitly use a Signature with the BC provider, the signature generation time still stays at around 10ms. What do you mean exactly by "the ECC provider"? If I do not register the `BouncyCastleProvider` with `Security`, I get the error `"ECDSA KeyPairGenerator not available"` when invoking `KeyPairGenerator.getInstance("ECDSA")`, so it seems to me that there is no default ECDSA support in Java. – Albert P May 11 '18 at 14:08
  • Try just "EC" for the key generator. – Maarten Bodewes May 11 '18 at 14:15
  • I'm getting about 1.792 ms for signing and 2.883 ms for verification on my machine, using SunEC on Java 8. Oh and 0.264ms and 0.442ms for Bouncy. I knew they were faster now but sheesh, that's an eye opener! – Maarten Bodewes May 11 '18 at 14:49
  • Oh, no, wrong iteration for the JIT warmup time: it's even better: 0.175ms and 0.386ms on Bouncy 1.57! – Maarten Bodewes May 11 '18 at 15:01

1 Answers1

1

Your method of testing is so far off that the results don't mean anything:

  • the JIT compiler should be allowed some startup time so it can compile and optimize the byte code;
  • many signs and later verifies of data need to be performed (separately), then time that and divide;
  • a nano timer or stopwatch implementation should be used;
  • the update should be part of the signature generation operation (or left out completely, signing the empty string);
  • ECDSA uses a SecureRandom implementation to calculate the signature, which may influence the result (better supply a specific one);
  • Beware that you may want to use the result for something, as smart compilers may otherwise optimize out the complete signature generation.

Finally, from a performance point of view:

  • Java 9 onwards have updated the BigInteger implementation with native code (intrinsics) which may make a huge difference in any asymmetric operations in software;
  • the standard SunEC provider may also be used with your curve and is more likely to perform well, again because it may use native instructions underneath (EDIT: but on my system using BC version 1.57 it is almost 10 times as slow!).

So note that the results are highly dependent on the environment and CPU used (write them down!).


You should use Signature.getInstance("SHA256withECDSA", "BC") so that the Bouncy Castle provider is actually used - if you don't do that and Bouncy is still used then you may have configured another Bouncy Castle provider within the JRE itself (in the security folder) so the results may be different if that's a different version than the one in your classpath.

Just specifying "EC" for the key pair generator is probably more compatible over different providers than "ECDSA" for some reason or other.


Finally, here's something to compare against:

$ openssl speed ecdsap256
Doing 256 bit sign ecdsa's for 10s: 147861 256 bit ECDSA signs in 9.98s
Doing 256 bit verify ecdsa's for 10s: 78064 256 bit ECDSA verify in 10.00s
OpenSSL 1.0.2j  26 Sep 2016
built on: reproducible build, date unspecified
options:bn(64,64) md2(int) rc4(16x,int) des(idx,cisc,16,int) aes(partial) idea(int) blowfish(idx)
compiler: gcc -I. -I.. -I../include  -D_WINDLL -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS  -DDSO_DLFCN -DHAVE_DLFCN_H -ggdb -O2 -pipe -Wimplicit-function-declaration -fdebug-prefix-map=/usr/src/ports/openssl/openssl-1.0.2j-1.x86_64/build=/usr/src/debug/openssl-1.0.2j-1 -fdebug-prefix-map=/usr/src/ports/openssl/openssl-1.0.2j-1.x86_64/src/openssl-1.0.2j=/usr/src/debug/openssl-1.0.2j-1 -DTERMIOS -DL_ENDIAN -O3 -Wall -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DRC4_ASM -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
                              sign    verify    sign/s verify/s
 256 bit ecdsa (nistp256)   0.0001s   0.0001s  14809.8   7806.4

On my lowly i7 based laptop:

processor   : 0
vendor_id   : GenuineIntel
cpu family  : 6
model       : 61
model name  : Intel(R) Core(TM) i7-5500U CPU @ 2.40GHz
stepping    : 4
cpu MHz     : 2394.000
cache size  : 256 KB
physical id : 0
siblings    : 4
core id     : 0
cpu cores   : 2
apicid      : 0
initial apicid  : 0
fpu     : yes
fpu_exception   : yes
cpuid level : 20
wp      : yes
flags       : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe pni dtes64 monitor ds_cpl vmx est tm2 ssse3 fma cx16 xtpr pdcm sse4_1 sse4_2 x2apic movbe popcnt aes xsave osxsave avx f16c rdrand lahf_lm ida arat epb xsaveopt pln pts dtherm fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdseed adx smap
clflush size    : 64
cache_alignment : 64
address sizes   : 39 bits physical, 48 bits virtual
power management:
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Might openssl be using SHA-1 for this test instead of SHA-256? – President James K. Polk May 11 '18 at 14:45
  • 2
    @JamesKPolk: [Neither (no hash at all)](https://github.com/openssl/openssl/blob/master/apps/speed.c#L1042) although you can use `speed sha1|sha256` to estimate what they would add; on my system (an even lowlier Celeron) it's about 1 part per thousand. – dave_thompson_085 May 11 '18 at 15:09