javamavendependenciespom.xmlbouncycastle

Are bouncycastle java libraries compatible with themselves?


I am using several third party libs which have dependencies to various bouncycastle libraries. Here some examples:

After updating one of these dependencies, I experienced very strange behavior at runtime, not finding methods or fields. It seems that a class of version X has been loaded by the classloader and after that an incompatible class of version Y could not find a field in version X.

So far so good. I tried to fix this using maven dependency management so that compatible versions are used at compile time.

I chose 1.79 for bc...-jdk15to18 and bc...-jdk18on and 2.73.7 for bc...-lts8on, because:

"Release 2.73.7 8 November, 2024 This release is based on BC 1.79 [...]" https://www.bouncycastle.org/download/bouncy-castle-java-lts/

so i thought this is safe, but actually it seems not to be safe. Still NoSuchFieldException, NoSuchMethodException and so on

I checked github mirrors for a Class which made some troubles:

You can see that the sources are different and declaring different public methods, e.g. SHA384Digest#newInstance().

EDIT

Since I answered the title question within the post itself, let me just clarify:

  1. There are many variants of bouncycastle.org libraries. In this case maven artifacts with identifiers ending with jdk18on and lts8on were obviously mixed together and classes of lts18on began calling classes for jdk8on. I can not provide a minimal reproducible example at the moment.
  2. Attention! Even though lts18on version 2.73.7 "is based on BC 1.79", they are not 100% compatible!
  3. This is not a bug. You have to manage your dependencies. Since libraries should use a stable API (or the Service Provider Interface for java.security.Provider) the "just use newest" approach should work.

Solution

  • Are bouncycastle java libraries compatible with themselves?

    No.

    First question: Should this considered to be a bug?

    No. In the sense of 'if you file a bug report it would be denied with "works as intended"/"wontfix" because BC isn't designed to operate properly when multiple versions of itself are all on the classpath'.

    And that goes for virtually all java libraries. Sticking two different versions of the same library on the classpath breaks stuff. Even if your code seems to run fine right now it's likely not to in some nook or cranny so don't ever do that.

    There are a few ways to solve the problem.

    Just use newest

    Just stick the newest release of BC on the classpath and nothing else. Build tools tend to do this automatically, or if not, have options to 'exclude' older versions.

    Now the requirement is that BC as a library is backwards compatible. Generally libraries are intended to be backwards compatible unless their documentation states that they are not (such as google's guava library which explicitly states it is not).

    However, be aware of Hyrum's Law:

    XKCD about Hyrum's law

    Any update is potentially capable of being incompatible. Just, usually, especially if the library author intends to be backwards compatible, it'll be fine.

    Runtime module systems

    OSGi is the most famous one; various appservers (think 'web server thing that hosts multiple java-written webapps' sometimes also have such functionality.

    Each classloader can be 'independent' of all others; you can load BC15 in one of them and BC18 in another, both are loaded simultaneously, and the same class (same name, same package) from 2 different versions can both be loaded. Whatever code you have that needs BC15 uses BC15, whatever uses BC18 uses BC18.

    The downsides are threefold:

    fatjar schemes

    Fatjar schemes will open a dependency (such as BC), take every class file, and rewrite it: It renames the package from org.bouncycastle to com.foo.yourapp.org.bouncycastle generally. This is a big mistake in the design of these fatjar schemes; it would have been much smarter to go with e.g. fatjar.c12355124123.org.bouncycastle where the c thing is a hash, or fatjar.v15.org.bouncycastle where the v thing is the full version string. That way, actually identical versions are merged and there will never be a conflict. 2 modules can talk to each other in terms of the dependency if they have the exact same version.

    Various fatjar tools can be configured to work like that. Or you insulate the BC15-needing stuff and the BC18-needing stuff and fatjar them independently so that the post-fatjar names don't overlap.

    fatjarring is annoying: It needlessly messes up your deployments. It takes much longer (unjarring, rewriting, rejarring into single massive jars, then transferring these giants to CI servers, test environments, and production), for no good reason as jars can list their own classpaths. There are also issues with how jars can be 'signed' and fatjar breaks this, and the BC jars are signed.

    Hence, this one is not recommended.

    Note that if you're already fatjarring or the deps you have are external and badly fatjarred, then you might have to commit to this option.