This is NOT a Flame!
I am not trying to flame or overly criticize the efforts of Daniel Fernández or the Jasypt team. I think their goal is a GREAT one, and their efforts are appreciated (~8,000 lines of Java, and ~13,500 lines of comments)! However, libraries like this worry me, and they should worry you too. Why? Because it seems to me from the documentation and code that the authors do not completely understand cryptography and have not gotten their efforts peer reviewed.
[Note: I disclosed this post to Daniel in advance of publishing it because I consider that the proper thing to do with security relating findings. As such Daniel and I have shared a few emails discussing this post. While he is not, and does not claim to be, a cryptography expert, he is MUCH more knowledgeable about cryptography than the code and documentation would initially indicate.]
Why Should I Believe You?
You Need to Know Something About Cryptography
What makes this library potentially dangerous is that someone not well versed in cryptography and the “gotchas” of cryptography will assume they are “being secure” by using such a library. In reality they’ve potentially opened themselves up to weaknesses they don’t even know exist. The unfortunate truth is that you need to have more than just a casual knowledge of cryptography if you plan on using it securely. If you plan on implementing a cryptographic library that hides details, then you need to be an expert in cryptography AND get your work reviewed by people more knowledgeable and experienced than yourself. It does not appear that has been done with this library, yet it seems to have a fairly wide acceptance and integration into other projects. [Note: Again from my emails with Daniel, there has been more peer review of this library than the documentation would lead you to believe.]
In the next few sections I will describe the issues I’ve found with this library. Two important statements before I continue: 1) I’ve probably missed other deficiencies in this library, if you find additional ones please leave a comment; 2) all is not lost, most of the issues can be easily fixed. [Note: The timing attack has already have been fixed.]
Hashing != Encryption
BasicPasswordEncryptor passwordEncryptor = new BasicPasswordEncryptor();
String encryptedPassword = passwordEncryptor.encryptPassword(userPassword);
…
if (passwordEncryptor.checkPassword(inputPassword, encryptedPassword)) {
// correct!
} else {
// bad login!
}
This seems straightforward enough. However, it shows a lack of the basic primitives of cryptography. Encryption, by definition, is “two-way” meaning that whatever is encrypted can be decrypted. (For a more rigorous and precise definition of Encryption and Decryption, see The Handbook of Applied Cryptography, Chapter 1.) When you dig into the code of BasicPasswordEncryptor you see that in fact they are actually using a hash function, not encryption at all.
textEncryptor.setPassword(myEncryptionPassword);
String myEncryptedText = textEncryptor.encrypt(myText);
…
String plainText = textEncryptor.decrypt(myEncryptedText);
When you dig into the code behind BasicTextEncryptor you find that it actually uses an encryption algorithm. At the very least this presents confusion. Developers might believe that a BasicPasswordEncryptor and a BasicTextEncryptor do the same thing when in fact they do not.
[From Daniel: The reason the word “encryption” is used there is basically marketing. Jasypt is not a library created for the security expert, but for the average developer. And a good percent of the average developers think of “password encryption” and not of “password hashing”. This is a term they usually understand better. And it is probably the way they searched it in Google when they reached the jasypt website.]
How can this be fixed? To start, renaming the PasswordEncryptor classes to something more appropriate like PasswordHasher or PasswordDigester would be a good start. Also, I would question why there is both a BasicPasswordEcnryptor and a StrongPasswordEncryptor? If the goal of this library is to prevent users from having to learn about cryptography, then why provide a choice? The differences are clearly documented (MD5, 8 byte salt, and 1K iterations for Basic and SHA-256, 16 byte salt, and 100K iterations for Strong), but now the user must know something about hash functions, salts, and the impact of iterations. This seems to go against the goal of the library.
Timing Attacks
If you look at the code for StandardByteDigester you will find the following lines at the end of the matches() method:
// Digest the message with the extracted digest.
The equals() method for the Arrays class is used to compare the two digests (incorrectly labeled as an “encrypted message”) to see if they are the same. The problem with using this method is that it opens the library up to a timing attack. This is the same bug found in the MessageDigest.isEqual(). Essentially an attacker can work byte-by-byte to discover the digest of the password.
It should be noted that the way in which Jasypt uses this method I believe to be safe. However, as it is a public method, a developer can choose to use it in any way he/she wants. And considering this library is intended to be used by people not well versed in cryptography, I think it should be fixed.
[From Daniel: So the only vulnerable scenario would be one in which this matches(message, digest) method is called being the message argument the one considered secret, and in the specific case that what the attacker is trying to obtain is a suitable hash that matches (incl. salt and iteration) the hash that would be produced for such secret message. Given this, I will admit that it would obviously be better if I change that line of code for a time-constant function, which I will do just in case.]
Fixing this particular issue is easy, just use MessageDigest.isEqual(). However, as is often true with attacks/issues, where there is one there are many. I would recommend again that this code be peer reviewed by folks who are more knowledgeable and experienced than myself. [Note: this was fixed in version 1.9.2.]
Weak Default Algorithms
MD5 is the default hash function for the BasicPasswordEncryptor. This is a poor default choice as MD5 is dead. In 2004 an attack by Xiaoyun Wang and Hongbo Yu was published that showed MD5 to not be collision resistant. In 2013 Xie Tao, Fanbao Liu, and Dengguo Feng published an attack against MD5 that reduced the complexity of the attack to a mere 2^18 operations. Unfortunately, MD5 is the chosen algorithm for the BasicPasswordEncryptor.
DES is the default algorithm for the BasicTextEncryptor. This is also a poor default choice as DES is dead as well. Unfortunately, only Triple-DES (and MD5) are being used in the StrongTextEncryptor. There is really no reason to use either MD5 or DES (or Triple-DES) when SHA-2/3 and AES exist.
With a library like Jasypt, the user expects the library to make the correct choices. That is the purpose of the library: “allow the developer to add basic encryption capabilities to his/her projects with minimum effort, and without the need of having deep knowledge on how cryptography works.” However, expecting the developer to pick the algorithm to use defeats the purpose of this library and forces the developer to know something about cryptography.
[From Daniel: Jasypt allows you to use ANY hash function or encryption algorithm implemented in your JVM security providers. The only thing you have to do is use jasypt’s “standard” digesters and encryptors and not the “easy wrappers” in the utils package, and then configure the algorithm of your choice.]
Conclusion
Daniel Fernández and the Jasypt team have the best of intentions with the Jasypt library. Unfortunately, the library has the deficiencies I enumerated in this post(and probably more than what I’ve documented here) and usually that is not a big issue with a library, but for a cryptographic library it is very troublesome. Using a cryptographic (or any security related library) library that has flaws lulls one into a false sense of security. The consumer feels that they are being secure, yet they are not.
Thanks Bill for taking my comments into account, and for taking the time of discussing your article with me.
Just as a further clarification on the timing attack issue, jasypt's password matching and general hashing operations were not affected by it. Anyway, as you note, this behaviour has been modified in Jasypt 1.9.2 just in case, in order to cover those unintended scenarios you talk about.
Regards,
Daniel.