Javascript Crypto Is Useful
In 2014, I wrote my first Javascript crypto library. Since then I've designed and written many such libraries - eight of which have been deployed in popular Google products and services. Javascript crypto can help solve many problems, some of which I'm going to discuss in this post.
Some people have strong feelings about Javascript crypto, and with good reasons. Writing crypto code in Javascript is difficult, because of the lack of types and the permissive nature of the run-times. You can always shoot yourself in the foot if you don't know what you're doing in any languages, but you don't even hear the shot in Javascript: the run-times don't usually 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 hostile to crypto code, as demonstrated in this neat exploit discovered by Bleichenbacher, the grandfather of many crypto attacks:
-
-
Encrypt this string: ưưưưưưưưưưưưưưưư as ASCII.
-
Decrypt the ciphertext.
-
XOR each character of the decryption with 0x52.
-
And the result is the key.
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 an array of only 256 elements.
Many libraries have this “type confusion” vulnerability. How do you fix it?
First, you need to validate input. If a function accepts a byte array, it should always check input is an array whose elements are bytes.
Secondly, you need to minimize type conversions. Instead of accepting strings in one function and big integers or byte arrays in others, we need to use a single type everywhere.
Thirdly, instead of plain old array of numbers you should use typed arrays. When a function accepts an Uint8Array it knows that each element of input is a byte, without doing expensive type checks.
Finally, you should use Google Closure and annotate variables and parameters with its pseudo types. The Closure compiler will type-check your program and report type mismatch bugs.
After fixing the typing issues, Javascript 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, or ActionScript, etc. I disbelieve that Javascript crypto is too bad that it is useless. 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 only 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. The article has a good point, but many other criticisms are outdated. Javascript now has a secure PRNG. WebCrypto provides many native 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 distrust the network or the server, doing Javascript crypto, with the code being loaded directly over that network from that server, makes you vulnerable to active network attackers or the server itself. 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 most applications. For example, as a Chrome extension End-To-End is immune against this threat. Next I’m going to showcase a few applications of Javascript crypto that are useful and secure.
1. Build crypto clients
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 amazing. 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, it's easy to forget that instead of installing an app to do something you can 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
Large scale systems usually process web requests as follows: Browsers <----> Load Balancers <---> Reverse Proxies <---> Frontends <---> Backends. If the requests contain credit card numbers, all intermediate components must comply to the PCI DSS standard, which could be annoying. To keep the load balancers, the reverse proxies or the frontends out of scope by you can use Javascript crypto to encrypt credit card numbers on browsers and only decrypt them on backends.
3. Avoid data leaks
Because many advertisers are slow in upgrading to HTTPS many advertisement serving systems still use clear text HTTP. Despite lack of encryption, these systems collect and transfer tons of personal data. Javascript crypto, served from a HTTPS origin, ensures collected data in motion are safe against interception.
4. Reduce latency
You have a big fat website that takes many seconds to load because it has megabytes of Javascript code. You may want to cache the base code in localStorage, and download only small diffs. This design is, however, vulnerable to cache poisoning attack. If an attacker manages to insert a backdoor to the cached code (by exploiting a XSS, for example) they will have permanent access (see this talk by my colleague Artur Janc for more details). You need to protect the integrity of the cached code by using a digital signature. You sign the code on the server, and use Javascript crypto to verify the signature.
Conclusion: Programming crypto in Javascript is hard, but doable. As usual if you don't know crypto, you should use good libraries developed by professionals. Javascript crypto has many applications, and has received unprecedented support from Stanford, Google, Microsoft and W3C.
Comments from the interwebs: Nate Lawson, Brad Hill, Hacker News. This great article explains why it's hard to maintain large code bases written in Javascript or other dynamic languages.
Encrypt this string: ưưưưưưưưưưưưưưưư as ASCII.
Decrypt the ciphertext.
XOR each character of the decryption with 0x52.
And the result is the key.
Comments
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 :)