What to do with Crypto?

870 views
Skip to first unread message

Will Sargent

unread,
Jun 25, 2015, 3:01:35 PM6/25/15
to play-fram...@googlegroups.com
Hi all,

There's been some discussion in the past about cryptography usage in Play contained in the Crypto package.

https://www.playframework.com/documentation/2.4.x/api/scala/index.html#play.api.libs.Crypto

The Crypto package does two things: symmetric encryption, and signing.

In 2.4, the play.api.libs.Crypto library was changed from AES/ECB/PKCS5Padding to AES/CTR/NoPadding and adding support for initialization vectors (IV):


This is great news (especially for anyone who has seen the ECB penguin), but there was a post to play-framework afterwards saying it was potentially more vulnerable due to malleability and key/IV reuse -- which is fine as long you're only encrypting / decrypting once, but is not the sort of thing that a user thinks about:


This is still being discussed, and the bug is here: https://github.com/playframework/playframework/issues/4407

This is an ongoing issue.  As an example, when Play 2.0 came out, it contained Crypto.passwordHash which did a single pass of MD5 instead of using scrypt/bcrypt/PBKDF2. Prior to Play 2.3.x, WS calls over HTTPS did not employ hostname verification, leaving it vulnerable to MITM attacks.  Fixing that and configuring JSSE so you could set up HTTPS without having to write code covered 13 pages of documentation.  Good crypto's just a ton of work.

Now there ARE cryptographic best practices (this is tqtf's version):


But as we've seen, even providing the "right" answer isn't enough without an API that prevents you from messing up.  Ideally, Play would have a CryptoAPI that wraps an industry standard high level crypto package, such as NaCl.

The problem with NaCL although it's a high level crypto interface, is that it's not portable.  It's a straight C implementation.  OpenDNS came up with Sodium, trying to make it a bit more portable, but it's still a C library, and therefore a non-starter for Play -- that includes wrappers like kalium.

Google at one point attacked this with Keyczar, which DOES have a Java implementation, but it looks like they haven't touched it in a couple of years.  jasypt's custom crypto "StrongPasswordEncryptor" <http://security.stackexchange.com/a/65240/6714> leads me not to trust it very much.

It's also possible to package the algorithms from the best practices together from BouncyCastle or from JCA.  I know the guide says "Don't use BouncyCastle" but I'm hoping there are more high level options than what he knows of:


This option is the least appealing, as it puts Play in the position of having to maintain crypto.  So I'm putting a call out to the community.  Anyone know what the right thing to do here is?

Will.


Will Sargent

unread,
Jun 25, 2015, 9:47:00 PM6/25/15
to play-fram...@googlegroups.com
To be clear: the proposal is to have a play.api.libs.Crypto trait, and have an adapter that would plug into KeyCzar, using DI.  The KeyCzar implementation would exist alongside the inhouse Crypto, and at some point they would switch.  

My preference is against a versioned approach (i.e. a Crypto implementation that can look at "version 1" vs "version 2" strings), because it's easy to weaken crypto by including old versions (i.e. FREAK) and adding extra logic to deal with it.

Rich Dougherty

unread,
Jun 25, 2015, 9:51:13 PM6/25/15
to Will Sargent, play-fram...@googlegroups.com
Have a Crypto trait with different implementations is a good idea. I wish we'd done that when we changed the encryption format in Play 2.4.

Here's a cautionary tale about about supporting different formats: https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/.

– Rich

--
You received this message because you are subscribed to the Google Groups "Play framework dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framework-...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Rich Dougherty
Engineer, Typesafe, Inc

Vít Šesták

unread,
Jul 1, 2015, 4:18:54 AM7/1/15
to play-fram...@googlegroups.com

I'd like to suggest something, but the correct solution depends on the purpose of the Crypto library. On one hand, it looks somewhat high-level (e.g. the output is String of printable characters and the API is simple), on some others, is does not look so high-level (the need of knowing the specifics of the mode of operation). It also depends on how much do we want to be backward compatible.


What output overhead is acceptable? In this case, I am mainly talking about some overhead with a constant upper bound. This overhead might be needed for padding, authentication and IV. The choice of CTR suggested that such several-bytes constant overhead might be too much, but I don't know the reasoning about the choice.


Random read/write: The CTR choice looks also like intended for some random read or write operations (note that random write with CTR is insecure except some special cases), but the API does not provide anything like this. If random access is a concern, it adds some limitations to mode of operations, so this is an important design decision. (The case of authenticated random write is the most tricky one, but it is still feasible at some cost.) I feel that random access is rather outside of the scope of the Play! Crypto.


Is authentication needed? Generaly yes (e.g. POODLE is an exploitation of an imperfect authentication), but users might have solved it separately, so authentication would bring an extra overhead (both computational and the output). Maybe authentication should be enabled (and required) by default, with an option to disable it.


Key derivation: If the key is strong, key derivation like PBKDF2 is rather an overhead with little-to-no security benefit. In some cases, such overhead might be a kind of DoS. On the other hand, when there is a weak password, a key derivation function like PBKDF2 might be a critical improvement of security. Unfortunately, if we want to address this issue, we seem to have to change the API. But this can be done in a conservative way by deprecating parts of the old API. (I might elaborate more about new API design.)


One of my major complaints about the current state is the global config. The user can configure the security settings for the whole app, but various parts of the app (some of them might be hidden in a 3rd party library) might have different needs. So either all of the parts of the app must be secure under any such configuration (which makes writing such code hard, requiring to be able to handle the lowest-common-denominator of all the modes) or user must be very careful when changing such global option (i.e. careful about all the requirements of all the code using the Crypto). So, I generally don't recommend changing the global config, as this might be very tricky.

 

My preference is against a versioned approach (i.e. a Crypto implementation that can look at "version 1" vs "version 2" strings), because it's easy to weaken crypto by including old versions (i.e. FREAK) and adding extra logic to deal with it.

 

I see you point, but I disagree in some degree. First, if you want to be backward compatible, you need versioning. Second, versioning does not imply the ability to downgrade the cryptography. There might be some potential to perform some oracle-based attacks (I might elaborate), but they are not much likely to be practical. Maybe a good tradeoff between complete versioning and not versioning at all is versioning when required. The user might configure the engines to be Seq(LegacyEngine1, LegacyEngine2, NewSuperSecureEngine). Such config would assign version numbers 1->LegacyEngine1, 2->LegacyEngine2, 3->NewSuperSecureEngine. The last one (i.e. the NewSuperSecureEngine) would be the one used for encryption. The first one (i.e. the LegacyEngine1) would be the one used for decryption if no version is available. When LegacyEngine1 is about to be switched off, one would just replace it by DisabledEngine (i.e. Seq(DisabledEngine, LegacyEngine2, NewSuperSecureEngine)), which would fail at all the decryption (and encryption) requests. This approach allows to balance backward compatibility and downgrade prevention. In the case of backward compatibility is required, I don't think that versioning would weaken the cryptography to lower degree than before.


Just a note about 3rd party crypto libraries: I'd like to have all the crypto primitived implemented in a native code if possible. The main reason for me is not the performance (although AES-NI can bring a serious speedup), but rather side channel resistance. This will probably favorize JCA and its wrappers. While JCA probably does not guarrant to be implemented in native code, the users are likely to get some well-reviewed native code implementation. (This might be false for some not-so-common JREs, but Play! can't address the issue without requiring a native platform-specific library.)



A comment on linked article http://security.blogoverflow.com/2013/06/qotw-47-lessons-learned-and-misconceptions-regarding-encryption-and-cryptology/:


Don’t use the same key for both encryption and authentication. Don’t use the same key for both encryption and signing.


I don't take this advice too generally. In fact, AEAD modes (which essentially do this) are rather recommended. I know two arguments against using the same key for both encryption and signing, none of them seems to be critical there. But there is some room for improvement. If we don't want to have multiple keys (which essentially requires application.secret/play.crypto.secret to be split to multiple config options), we might use some subkey generation for that. This will require versioning when the backward compatibility is needed, but in such case, the versioning would be probably needed anyway. (I may elaborate.)


Regards,

Vít Šesták 'v6ak'

Rich Dougherty

unread,
Jul 1, 2015, 8:06:14 PM7/1/15
to Vít Šesták, play-fram...@googlegroups.com
On Wed, Jul 1, 2015 at 8:18 PM, Vít Šesták <groups-no-private-mail--con...@v6ak.com> wrote:

What output overhead is acceptable? [snip]

The main use case is encrypted/signed tokens and cookies. These are going to be sent over the network. All things being equal we should try to use fewer bytes, so long as we can do so without compromising security. For example, prefer using base64 rather than hex encoding, because the encoding is smaller. However, if we need to pad to 8 bytes to be more secure then we can do that. We can trade off extra size if we feel it's worth it.

Random read/write: [snip] I feel that random access is rather outside of the scope of the Play! Crypto.

I agree that random read/write is outside the scope. We're mainly looking at working with short strings and arrays of characters or bytes that we fully encode and decode each time we work with them.

Is authentication needed? [snip] Maybe authentication should be enabled (and required) by default, with an option to disable it.

Sounds good. 

Key derivation: If the key is strong, key derivation like PBKDF2 is rather an overhead with little-to-no security benefit. [snip]

FYI default Play secret is generated by SecureRandom. It's a 64-character long string, with each character chosen from 75 possible values. See ApplicationSecretGenerator.scala. To me it seems like the secret is strong enough that we don't need too much extra key strengthening, but I may be wrong.

One of my major complaints about the current state is the global config. [snip]

Now that we support dependency injection in Play it should be easier to have multiple Crypto implementations in a single application. We can have a default implementation, we can also have @Named implementations that users can import into controllers in different parts of their applications.

We could have configuration like:

crypto.default {
  type = …
  secret = "..."
}
crypto.banana {
  type = ...
  secret = ...
}
crypto.apple {
  type = ...
  secret = ...
}

Which are used like:

class Controller1(crypto: Crypto) { ... }
class Controller2(crypto: @Named("banana") Crypto) { ... }
class Controller3(crypto: @Named("apple") Crypto) { ... }

Just a note about 3rd party crypto libraries: I'd like to have all the crypto primitived implemented in a native code if possible. [snip]

Fair point.

Don’t use the same key for both encryption and authentication. Don’t use the same key for both encryption and signing.

I don't take this advice too generally. [snip]

I don't have a strong opinion on this, but if we want to have multiple keys then it's relatively trivial for us to implement. We would just need to change our key generation code.

Regards,

Vít Šesták 'v6ak'

Thanks for your comments.

Cheers
Rich

Vít Šesták

unread,
Jul 15, 2015, 4:32:23 PM7/15/15
to play-fram...@googlegroups.com, groups-no-private-mail--con...@v6ak.com
I am sorry for the delay, I was offline for some time.

 

What output overhead is acceptable? [snip]

The main use case is encrypted/signed tokens and cookies. These are going to be sent over the network. All things being equal we should try to use fewer bytes, so long as we can do so without compromising security. For example, prefer using base64 rather than hex encoding, because the encoding is smaller. However, if we need to pad to 8 bytes to be more secure then we can do that. We can trade off extra size if we feel it's worth it.

Is authentication needed? [snip] Maybe authentication should be enabled (and required) by default, with an option to disable it.

Sounds good.

When thinking about AEAD modes, it looks like separate authentication will be better:
* Separate authentication will not produce any additional space overhead compared to AEAD.
* It is easier to provide optional authentication if it is separate.
* There are some complaints about AEAD security: It uses the same key for authentication and encryption. Principles of authentication and encryption are related. So, if there is something wrong (either in implementation or in the design itself), it will more likely practically break. See http://blog.cryptographyengineering.com/2011/12/matt-green-smackdown-watch-are-aead.html .

Maybe we should use some mode like CBC (don't take it as the only option), maybe with ciphertext stealing in order to reduce the space overhead. For authentication, HMAC should be OK.

Of course, ciphertext stealing reveals original message length more precisely than padded modes, as the length of the ciphertext is not rounded up to whole 128-bit blocks. But this is something that user has to be aware of. Hiding message length is something that cryptography can't generally provide. Cryptography can blur it at most.

This, however, reminds me one potential cryptographic pitfall: compression. While compression arguably can blur message length, it also can provide valuable side channel in some scenarios – see BREACH or CRIME attacks. Whichever solution we choose for Play! crypto, I'd like to avoid compression. Besides those cryptographic reasons, there are some other practical reasons – compression is rather useless on short inputs and makes hard to compute upper bound of storage needed.
 

Key derivation: If the key is strong, key derivation like PBKDF2 is rather an overhead with little-to-no security benefit. [snip]

FYI default Play secret is generated by SecureRandom. It's a 64-character long string, with each character chosen from 75 possible values. See ApplicationSecretGenerator.scala. To me it seems like the secret is strong enough that we don't need too much extra key strengthening, but I may be wrong.

That's true for application secret. Note that breaking the password might be harder than breaking the key. (The secret looks like having >398 bits of entropy. The actual entropy might be lower, as SecureRandom might be the weakest link, but even in that case, it should be strong enough.)
However, Play! offers using a custom password and there are not any guidelines about that, as far as I've seen. So user might feel fine passing user-provided password there, which is in many cases not so fine… So either:
* the user should be aware of such limitation (and maybe the API should feel more low-level like key: Array[Byte] instead of password: String) or
* user-provided passwords should be somehow strenghtened (e.g. PBKDF2).

Regards,
Vít Šesták 'v6ak'

Will Sargent

unread,
Feb 29, 2016, 5:47:25 AM2/29/16
to Play framework dev, groups-no-private-mail--con...@v6ak.com
Following up on this, Crypto has been broken apart into "CSRFTokenSigner", "CookieSigner", "AESCrypter" traits that are tailored for the Play use case, and the Crypto singleton object has been deprecated.

The commit is here:


and the Crypto Migration guide (with more technical background) is here:


Thanks especially for Vit for his assistance and review.

Will.
Reply all
Reply to author
Forward
0 new messages