Profile picture
Ben Kuhn @benskuhn
, 24 tweets, 4 min read Read on Twitter
One way of learning good API design is to look at the decisions good APIs make. Another is to use a bad API and regret all their decisions. Today let’s try the second one on my favorite API tire fire, javax.crypto.
So you want to encrypt some data! OK, it looks like the Cipher class will do what we want. Let’s create one via Cipher.getInstance(“AES/CBC/PKCS5Padding”)! Except, oh shit, I have to catch two different exceptions:
NoSuchAlgorithmException and NoSuchPaddingException. Even though I hardcoded the algorithm and padding to a value that I know exists. WHAT IF AES STOPS EXISTING GUYS.
Ok, I have my Cipher and my 4 lines of try/catch boilerplate! Let’s encrypt some stuff!

NOPE. I still have to init() my Cipher. But which of the 8 init() overloads do I call??
(Man, if only there were some way I could construct this Cipher with a single call, instead of having to getInstance() and then remember to init() afterwards. Since it constructs the object, maybe we could call it a “constructor”?)
OK, I don’t care about supplying my own SecureRandom so at least I can cut it down to 4 overloads. That’s the easy part.
Of the other 4, 1 of them will throw a runtime exception... and 1 of them will appear to work but might introduce a security vulnerability (null IV) on some platforms. Let’s stay away from those ones 💩🔥
Now the only question I need to answer is: what’s the difference between an AlgorithmParameters and an AlgorithmParameterSpec? Unfortunately, the Javadoc for these 2 init() overloads is LITERALLY THE SAME so I have no idea 😖
Ok, the AlgorithmParameters constructor is protected so if I want to use it I’m going to have to do the same getInstance()/init() junk on that one too. Nope nope nope. Let's try the AlgorithmParameterSpec one...
Ah fuck, AlgorithmParameterSpec is an interface with 19 implementers. Which one do I need to supply for AES!?

Using my Domain Knowledge™ I deduce that the answer is an IvParameterSpec because AES needs an IV.
If only there was some way the compiler could tell me if I was invoking this method with the wrong kind of object! LIKE A STATIC TYPE SYSTEM WHICH I HEAR IS A THING IN JAVA 🤦‍♂️
Aaaand now I need to catch InvalidKeyException and InvalidAlgorithmParameterException.

Sure is too bad that there’s no way to *statically* tell the compiler that I used the right *type* of AlgorithmParameterSpec and this exception would never happen...
Whew! Finally I have an initialized cipher and I can make it encrypt my string with cipher.doFinal(myString.getBytes()), the second most generic possible method name.
("Why is it called doFinal, Ben?" Glad you asked! Because we passed the encrypt-vs-decrypt switch as a "mode" parameter to init() earlier. Just in case you wanted the same code path to sometimes encrypt the data, and sometimes decrypt. You never know!)
And for the cherry on top, for doFinal() we have to catch an IllegalBlockSizeException and BadPaddingException, even though both of them provably can’t be thrown (the 1st because we requested PCKS5 padding and the 2nd because we’re encrypting).
For those of you following along at home, we wrote 3 lines of actual code--2 for initialization and 1 for encryption--and 6 lines of “catch” statements that we know will never execute, because this API is MAXIMALLY GENERIC.
So generic that they had to worry about what happens if you combine the generic pieces in an invalid way... and the result was 6 different arcane checked exceptions.
Why is there a parameter for encrypt vs decrypt? Why do we call Cipher.getInstance().init() instead of new AESEncryptor()? Why is the init() parameter an interface with 19 implementations that do completely different things?
The designers had (arguably) good reasons for making it so generic, as you will learn if you read their “Architecture Reference Guide” (eep)--they wanted “algorithm independence and extensibility”: docs.oracle.com/javase/7/docs/…
Presumably, they wanted to support the case where you would be supplying the cipher string and parameters at run time, perhaps with a value from a config file, specifying a special algorithm only implemented by your particular JRE version.
But in the service of extensibility, they made something that should be easy, hard. IMO, the crazy generic string based API should have been implemented on top of a straightforward, statically typed API.
More generally, you can make your APIs better by doing the reverse of javax.crypto: make impossible operations fail to type check! And use as many classes, methods, etc. as you need to make it ergonomic.
No getInstance() with a string. No init() with one of 19 subclasses. Separate Cipher subclass for every algorithm. Separate methods for encrypt and decrypt (and don’t name them “do”). Convenience classes and methods are cheap!
Oh, and maybe go easy on the checked exceptions 😀
Missing some Tweet in this thread?
You can try to force a refresh.

Like this thread? Get email updates or save it to PDF!

Subscribe to Ben Kuhn
Profile picture

Get real-time email alerts when new unrolls are available from this author!

This content may be removed anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member and get exclusive features!

Premium member ($3.00/month or $30.00/year)

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!