bc-java icon indicating copy to clipboard operation
bc-java copied to clipboard

Performance issue with BCFips security provider

Open chavdarch opened this issue 3 years ago • 5 comments

Hi team,

My company is planning to use bouncy castle to enforce fips compliance for encryption. I've used a simple jmh benchmark that is running in a loop with a lot of encryptions/ decryptions using java cipher for AES. I ran the attached benchmark on my machine(mac) and on our local bamboo server(linux) and got the following results mvn clean -B verify --file pom.xml exec:java

Benchmark                                      Mode  Cnt        Score        Error  Units
EncryptionBenchmark.fipsBcProviderEncryption  thrpt   25    32008.062 ±   3524.149  ops/s
EncryptionBenchmark.nonfipsEncryption         thrpt   25  3235114.417 ± 136671.753  ops/s

where nonfipsEncryption is using SunJCE version 16 security provider and fipsBcProviderEncryption is using BCFIPS version 1.000203

So it looks like BCFIPS is about 100x slower(i. terms of ops/sec) than the standard SunJCE.

I also did some profiling on the same test and it looks like the hotspot is in org.bouncycastle.cryptor.fips.AESEngine.generateWorkingKey where 62 % of the cpu time is spent.

Do you have any suggestions/recommendations on how to improve the performance of the BCFips?

cchernashki-bc-fips-benchmark

Screen Shot 2022-04-12 at 1 02 55 pm Screen Shot 2022-04-12 at 1 04 43 pm

In comparison this is how the same test looks like with the Sun Jce Screen Shot 2022-04-12 at 1 16 53 pm Screen Shot 2022-04-12 at 1 18 31 pm

chavdarch avatar Apr 12 '22 08:04 chavdarch

The results differ depending on the OS/JVM/hardware, do you have any recommendation for the hardware?

chavdarch avatar Apr 12 '22 11:04 chavdarch

The first thing I noted about the benchmark is that the cipher is initialized for every encryption/decryption (which are very small texts) with the same key. It's unnecessary to re-initialize unless changing the key (or when the cipher is being switch from encryption to/from decryption).

We don't have an optimization to avoid the expensive work of generating the working key if the key hasn't actually changed since the last call. It's possible the SunJCE one does have this.

Could you please modify your benchmark (or add a second one) that avoids these repeated calls to Cipher.init, and report the results? The difference will still be significant since SunJCE is probably forwarding to a native implementation (if not AES-NI hardware implementation).

peterdettman avatar Apr 12 '22 11:04 peterdettman

Thanks a lot for your suggestion. I have added additional test, that have separate ciphers for encryption and decryption, that are initialised once at setup

https://gist.github.com/chavdarch/85ca0851e4f5b157972ee918e0d2e761

and yes, that improved significantly the throughput.

Benchmark                                                    Mode  Cnt        Score        Error  Units
EncryptionBenchmark.fipsBcProviderEncryption                thrpt   25    32765.990 ±   3777.521  ops/s
EncryptionBenchmark.fipsBcProviderEncryptionLessCipherInit  thrpt   25  1163069.649 ±  18342.893  ops/s
EncryptionBenchmark.nonfipsEncryption                       thrpt   25  3030303.132 ± 141339.365  ops/s

fipsBcProviderEncryptionLessCipherInit is now just ~3x slower than nonfipsEncryption.

This is great, unfortunately we are using aws encryption sdk and in the call chain I can see a call https://github.com/aws/aws-encryption-sdk-java/blob/master/src/main/java/com/amazonaws/encryptionsdk/internal/CipherHandler.java#L57

which is calling init and then doFinal in the same way as the fipsBcProviderEncryption so i guess that needs to be patched, unless bc has a way to optimise the Cipher.init.

Screen Shot 2022-04-12 at 11 12 04 pm

chavdarch avatar Apr 12 '22 13:04 chavdarch

We could look at improving checks for same-key initialization (for AES/GCM I think this is something of a known issue already), but you likely couldn't expect a new version of the FIPS provider with such a feature before next year.

Unfortunately the CipherHandler case cannot really be "fixed" because at least the nonce will need to be changed for each operation, so the init calls cannot be avoided. You could check whether CipherHandler is actually re-used though, maybe there's a different key for each operation anyway (I don't know the context).

peterdettman avatar Apr 13 '22 10:04 peterdettman

thank you for your answers! We will wait for the new version when it comes and in the meantime we will see what can we do with the encryptions sdk

chavdarch avatar Apr 13 '22 20:04 chavdarch

Appears resolved.

dghgit avatar Jul 22 '23 03:07 dghgit