Wednesday, June 18, 2014

Why Javascript Crypto Is Useful

Update: Nate Lawson published a thoughtful comment on this article.

Update: A few more thoughts from Brad Hill of PayPal and W3C.

Update: why it's hard to maintain large codebases written in Javascript or other dynamic languages.

It's approximately a year ago when I wrote my first Javascript crypto library for a production system at Google - it's a thin wrapper on top a library that has been released as End-To-End. Since then I've designed and written many such wrappers - eight of which have been deployed in popular products and services. I think Javascript crypto is a very useful tool that could help solve a wide range of problems, some of which I'm going to discuss below.

Some people have strong feelings about Javascript crypto, for good reason. At the end of the day Javascript is the language of the Web, so like it or not Javascript crypto won't go away. As somebody who likes crypto and security the only thing that I figure I could do is to study to understand the problems and fix them.

Writing crypto code in Javascript is indeed difficult, because of the lack of types and the permissive nature of the run-times. It's always easy to shoot yourself in the foot if you don't know what you're doing in any languages, but in Javascript you don't even hear the shot: the run-times usually don't complain, but they just silently give you an incorrect result.

For example, if you have an array x of 10 elements, accessing x[10] or x[11] won't throw an out of bound exception, but return undefined. This behavior is very hostile to crypto code, as demonstrated in this super neat exploit discovered by Bleichenbacher, the grandfather of many crypto attacks:
* Visit http://people.eku.edu/styere/Encrypt/JS-AES.html. This is a Javascript implementation of AES.
* Encrypt this string: ưưưưưưưưưưưưưưưư as ASCII.
* Decrypt the ciphertext.
* XOR each character of the decryption with 0x52.
* And the result is the key.

So what happened? The program expects ASCII inputs, but we give it Unicode. The function that causes the vulnerability is SubBytes

// do S-Box substitution
function SubBytes(state, Sbox)
{
   var i;

   for( i=0; i<16; i++ )
      state[i] = Sbox[ state[i] ];

   return state;
}

The state variable holds the input. If state[i] >= 256, state[i] would become undefined because Sbox is defined as an array of only 256 elements. Apparently this problem doesn't just apply to that particular library - there are plenty of others. There's a few ways to take care of it.

The first thing you can do is to extensively check the inputs and limit the number of places where you have to convert types. If you want a byte array, you should always check that it's an array and each element is a byte. The type of parameters in public interfaces should be consistent. You don't want to accept strings in one place, and big integers or byte arrays in others. There's still a few places in the End-To-End's library that could be more consistent, and we're going on that.

You can also use typed arrays, instead of plain old array of numbers. When you see an Uint8Array you know for sure that each element is a byte, so don't have to perform the expensive check. Unfortunately Internet Explorer < 10 doesn't support typed arrays, so it's a bit ugly to use them right now. For that reason we haven't supported typed arrays in the End-To-End's library, but we're working on that too.

Finally you can use Closure's pseudo types. You can annotate variables with types, and the Closure compiler uses these annotations to type-check your program, which helps catch type mismatch bugs early during the development. End-To-End's library is built on Closure, so we have pseudo types since day one.

After fixing the type problem, Javascript then could only be as bad as other languages when it comes to XOR things together. I've seen broken crypto in C, C++, C#, Java, Ruby, Python, ActionScript, etc. so I don't really buy the argument that Javascript crypto is too bad that we shouldn't even try to do anything with it. The language has its annoying flaws, i.e., numbers are stored as floating point in a 52-bit mantissa, broken bitwise shift left, etc., but they just make the task of programming a crypto library a bit more fun and challenging, not riskier.

Most people consider Javascript crypto harmful after reading this article by Matasano Security (disclaimer: I worked at Matasano before joining Google, and I've always been a huge fan of @tqbf, one of the company's founders.) If you haven't go read it, I'll wait. I don't know when the article was written, but many of its criticisms are outdated. Javascript now has a secure PRNG. WebCrypto is going to provide a lot of native implementation of common crypto primitives, including a secure key storage. Guess what do Stanford, Google, and Microsoft have in common? Each has released an open source Javascript crypto library. No other languages have enjoyed this tremendous support.

That said, I totally agree with its main criticism: if you don't trust the network or the server, doing Javascript crypto, with the code being loaded directly over that network from that server, won't help safeguard your data against active network attackers or the server itself. That's why End-To-End will be released as a Chrome extension. In other words if the server or the network is considered as adversary in your threat model, don't trust Javascript crypto code delivered from them. This threat model doesn't, however, apply to all applications. Below is a few of many such applications that I've seen.

1. Crypto browser apps

Without Javascript crypto you don't have SSH-In-A-Tab, PwdHash, various Bitcoin wallets, etc.

Do you have a Chromecast? If you don't, go buy one. It's an amazing product, and did you know that it uses Javascript crypto to make the out of box setup so easy and yet secure?

We're living in a world dominated by apps, so it's easy to forget that instead of installing an app to do something you can just visit a website on your favorite browser and get the same thing done instantly, thanks to the power of Javascript.

2. Stay out of scope of PCI DSS

Here's how a request is typically processed in a large scale system: Browsers <----> Load Balancers <---> Reverse Proxies <---> Frontends <---> Backends.

If you don't want to make your load balancers, reverse proxies, frontends, etc. fall within the scope of PCI DSS you want to use Javascript crypto to encrypt credit card data on the browsers, and only decrypt them on the backends.

You also want to use Javascript crypto if the user inputs are so sensitive that you don't want them to be accidentally logged on any intermediate components.

3. Avoid leaking data to third-party

When you collect data with Javascript, and pass to server in URL parameters you could leak them to third-parties, e.g., via the Referer header. So you want to encrypt them using a public key encryption scheme, e.g., RSA, in Javascript.

4. Reduce latency

You have a big fat website that takes many seconds to load because it has to load megabytes of Javascript. The Javascript code is changed very frequently so caching doesn't help much. One solution is to cache the base code in localStorage, and just download a small diff next times. But if you do that, you may never recover from a compromise. If the attacker manages to insert a backdoor to the cached code of a user (by exploiting a XSS, for example) he'll forever have access to that user's account (see this talk by my colleague Artur Janc for more details).

So you want to verify the integrity of the cached code, and you can do that by digitally signing the cached code, then using Javascript on browsers to verify the signature. You can trust the Javascript code because it's freshly served by your server.

In general returning signed data and code to browsers, then verifying them with the Javascript code loaded from trusted origins could significantly help reduce latency by cutting the number of requests as well as the size of responses.

Conclusion: Programming crypto in Javascript is hard, but doable. As usual if you don't know much about crypto, you should use good libraries developed and maintained by people in the know. Javascript crypto has received unprecedented support from Stanford, Google, Microsoft and W3C, as it has been proven super useful in many applications.

4 comments:

Minh Hoàng said...

Chắc phải cải thiện khả năng đọc hiểu tiếng Anh của mình quá -_-!

Ankur said...


Nice,
Thanks for your greatful informations, working in, ASIAN AFFAIRS MAGAZINE.
URDUTAHZEEB.
Try to post best informations like this always

Thai Politics: Is Thailand out of Yingluck as instability looms?


Unknown said...

I hope more stable JS Crypto libraries are released. There's a great need of them for internet users.

It's the only way to trust that your data is actually being encrypted, instead of blindly believe what the company in question wants to sell you.

I'm not near to being literate about these topics, but wouldn't a browser extension checking if the crypto code changes from a trusted initial version of it and/or checking its hash against a server stored hash be enough?

Thanks for your efforts in making JS crypto possible :)

Unknown said...

Enough to check its integrity, I mean.