Skip to content

Conversation

william-tran
Copy link

Fixes #2917

I ran a very cursory JMH benchmark on encrypt/decrypt of a 1 MB byte array of random bytes and this implementation is 3x slower than JCE, and for a 1 kB byte array this is 4.7x slower.

@william-tran william-tran force-pushed the bouncycastle-aes branch 5 times, most recently from 1e1eb8e to 1358da4 Compare April 1, 2016 16:23
}

public BouncyCastleAesBytesEncryptor(String password, CharSequence salt) {
this(password, salt, null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not use a null IV as this is not safe. We always need to create IV for AES/CBC.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches the behaviour of the NULL_IV_GENERATOR in AesBytesEncryptor, which is shown in BouncyCastleAesBytesEncryptorEquivalencyTest.bcJceWithoutIvEquivalent(), and Encryptors.queryableText produces this sort of encryptor.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is also incorrect (but we cannot change).

NOTE Also note there is a slight difference given AesBytesEncryptor is package scope and the static methods are a bit more descriptive. Technically though this is still broken and we should not repeat it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so I'll remove that constructor and expect an IV generator in the code. Should I also make it package scope and final and add a method to Encryptors?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave it public. I think the static methods in Encryptors need to go eventually. The problem with them is that what is "standard", "stronger", etc changes with time. However we cannot modify these methods and stay passive.

@william-tran
Copy link
Author

I ran some more benchmarks comparing using a new instance of BlockCipher per invocation vs synchronized reuse of the BlockCipher. The test uses 4 threads sharing the same BouncyCastleAesBytesEncryptor, with 10 sec warmup, and 10 sec sampling.

synchronized reuse:

Benchmark                        Mode  Samples      Score      Error  Units
o.s.MyBenchmark.bcEncryptor     thrpt       10  21407.710 ±  855.481  ops/s
o.s.MyBenchmark.jceEncryptor    thrpt       10  92248.023 ± 2361.111  ops/s

new

Benchmark                        Mode  Samples      Score      Error  Units
o.s.MyBenchmark.bcEncryptor     thrpt       10  44368.225 ± 2895.580  ops/s
o.s.MyBenchmark.jceEncryptor    thrpt       10  92362.021 ± 5653.288  ops/s

That's 2x throughput than synchronized. I ran it off my dual core macbook air so the numbers make sense given 4 threads.

Then I tested the GC differences with a single thread running 1 million test cycles, against -Xms1g -Xmx1g -XX:+UseParallelGC. jvisualvm reports these numbers:
BC synchronized:
image

BC new:
image

JCE (synchronized):
image

All three show very similar minor GC counts (23, 25, 24), and actually JCE shows higher GC cpu time (50, 57, 81) ms.

Given all this, I think using a new instance of the cipher per invocation is the way to go. So I think that's it, should I squash the commits?

@rwinch rwinch added status: duplicate A duplicate of another issue in: crypto An issue in spring-security-crypto type: enhancement A general enhancement labels Apr 4, 2016
@rwinch rwinch added this to the 4.1.0 milestone Apr 4, 2016
@rwinch rwinch self-assigned this Apr 4, 2016
@rwinch
Copy link
Member

rwinch commented Apr 4, 2016

A few additional comments:

  • I think we should provide a constructor with a default IV generator that makes it easy for users
  • We should likely provide a GCM mode since this is the preferred mechanism today

@rwinch rwinch added the status: waiting-for-feedback We need additional information before we can continue label Apr 4, 2016
@william-tran william-tran force-pushed the bouncycastle-aes branch 4 times, most recently from 07925f4 to 18d7a71 Compare April 5, 2016 21:41
@william-tran
Copy link
Author

Github was having issues yesterday and seems to not be kicking off Travis... lemme see if I can make a few force pushes to get that going again

@rwinch
Copy link
Member

rwinch commented Apr 7, 2016

@william-tran Thanks for the updates!

Looking at the code it appears my suggestion to use a base class might have caused more code/complexity than intended. I'm wondering if the base class makes sense. Do you think we could try to:

  • Remove the need for OutputStreams
  • Remove passing in a boolean for ENCRYPT or DECRYPT. To me this is confusing even if the BC APIs define it this way. I'd much rather have some duplicate code.

I think the best way we could do this is to create the two classes totally independent of one another (without thinking about the other code). If there are similarities those can be extracted into a package private superclass. If not, then they live in their own classes.

@william-tran
Copy link
Author

The whole need for OutputStreams is so that method can return two objects, a properly sized ByteArrayOutputStream and a properly initialized CipherOutputStream, then common code can process those. Both those objects depend on a BlockCipher that doesn't share a common subclass. (though have the same method names). The process method is a bit ugly due to the need to try/catch/finally/close streams so I didn't want to repeat that. While at first glance what you're asking for would seem clearer, it'll result in the 4x duplication (encrypt/decrypt, CBC/GCM) of a lot of complexity.

@rwinch
Copy link
Member

rwinch commented Apr 7, 2016

@william-tran Thanks for the response. I'm much prefer the duplication of the code to needing to add classes like OutputStreams which adds its own variation of complexity.

@william-tran
Copy link
Author

I prefer the previous revision, but what matters the most to me is that it works. We can entirely eliminate the common class by duplicating key generation and OutputStream processing, I only went half way.

@rwinch rwinch removed the status: waiting-for-feedback We need additional information before we can continue label Apr 12, 2016
byte[] process(CipherOutputStream cipherOutputStream, ByteArrayOutputStream byteArrayOutputStream, byte[] bytes) {
try {
cipherOutputStream.write(bytes);
cipherOutputStream.close();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cipherOutputStream is closed in the finally block on line 69. Is it intentional to close cipherOutputStream twice?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second call could go in the catch instead of a finally. close() does a doFinal and releases resources, in case the write() throws exception we should try to release those resources

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved the second close() to the catch block and added comments to clarify things.

@william-tran william-tran force-pushed the bouncycastle-aes branch 2 times, most recently from b6d8e28 to 9b0a758 Compare April 13, 2016 15:00
cipherOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
catch (IOException e) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case this isn't an IOException, we won't attempt to release resources and wrap in IllegalStateException. What about just catching any Exception?

Implments "AES/CBC/PKCS5Padding" and "AES/GCM/NoPadding"

Fixes spring-projectsgh-2917
@rwinch rwinch merged commit 63b2cfe into spring-projects:master Apr 13, 2016
@rwinch
Copy link
Member

rwinch commented Apr 13, 2016

Thanks for the hard work @william-tran! This is now merged into master

@william-tran
Copy link
Author

Woohoo! Thanks for guiding this to landing.

@william-tran william-tran changed the title BouncyCastle implementation of "AES/CBC/PKCS5Padding" BouncyCastle implementation of "AES/CBC/PKCS5Padding" and "AES/GCM/NoPadding" Apr 14, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: crypto An issue in spring-security-crypto status: duplicate A duplicate of another issue type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants