In the previous post, I talked briefly about how passwords are usually a bad way to create keys for encryption. One alternative method is Diffie-Hellman key exchange, which I ported to Browserify in this library.

Basic Idea

Two people can calculate a key, known only to them, communicating publicly. Here’s how it works, using some absurdly small numbers you shouldn’t even think about using in reality, as my cats could crack them:

You agree on a base and a prime, for example 2 for the base and 35963 for the prime. Then you calculate a random number, say 26971 . This is your private key. Then calculate your public key, which is the base, raised to the power of your private key mod the prime (mod meaning divide by the prime and take the remainder). In JavaScript, this would be: Math.pow(2, 26971) % 35963 This is your public key and is 8182 . You send your public key to your friend, then they send you their public key 32789 . You compute their public key, raised to the power of your private key, mod the prime or: Math.pow(32789, 26971) % 35963 Meanwhile they do the same thing, assuming their private key is 17226 : Math.pow(8182, 17226) % 35963 Both of you calculate that to be 28001 which should be impossible for somebody else to calculate based just on 2 , 32789 , 8182 and 35963 . (Although in this example they could, because the numbers are so small.)

Advantages

Diffie-hellman is somewhat lesser known then other asymmetrical cryptography methods like RSA, so what advantages does it have? Its main advantage is that it allows for forward security, with the disadvantage being that it requires a input from both parties before encryption happens.

We will cover RSA in more details in a later post, but what you need to know is just that RSA is based on encryption where there are two keys: one for encrypting and one for decrypting. To send an encrypted message, one party encrypts the key with the public key of the other party and then sends the encrypted message along with the encrypted key to the other one.

With Diffie-Hellman, the recipient would calculate a temporary private key and use that to create a public key, and give that to the sender. The sender would calculate a temporary private key and use that to create a public key and shared secret, then use the secret to encrypt the message. Then they would forward their public key and the encrypted message to the recipient and throw out the temporary keys. (Both parties would also have more permanent public keys they’d use for signing their messages, we will cover that in a later post.)

You should be able to see why RSA is popular with encrypted email; you can fire off an encrypted email without the recipient needing to do anything except post their public key somewhere. But it doesn’t have “forward security,” because if the recipient’s private key is ever compromised, somebody could use that to decrypt the message, since they key is right there.

With Diffie-Hellman, on the other hand, since only permanent keys are used to authenticate the message, if a key is compromised, you can’t use that to decrypt old messages. But the drawback is that you need interactive communication to establish the shared secrets, which makes it unwieldy for things like email. There is also an overhead of having to come up with new keys for every message.

Big numbers

If you tried running this example in your console, you would have noticed that Math.pow(2, 26971) % 35963 returns NAN . This is because, in JavaScript, Math.pow(2, 26971) returns Infinity . Thus some sort of “big number” library for dealing with math operations on integers of arbitrary giganticness is mandatory for all JavaScript encryption libraries. I looked at a bunch and bn.js by Fedor Indutny was the only standalone big number library that had the optimizations necessary for this kind of crypto.

Pow mod

Hopefully you have realized that doing

var powered = Math.pow(2, 26971) var result = powered % 35963;

is not the most efficient way to calculate a pow b mod c as the intermediate result is

(Calculated with this Erlang script.)

Bearing in mind that the huge number above is for the ridiculously small keys that we are using as examples, how can you calculate it in a realistic manner?

The simplest way is:

This breaks down when b a decimal number 200+ digits long, which is the case in a more realistic scenario of a 768-bit prime number. (35963 in our example is 16 bits long.) There are various ways of speeding this up, and I’ve spent two days trying to come up with some examples that I can understand well enough to explain to a non-math person but can’t.

What you need to know is that bn.js is fast due to the Montgomery reduction, which is a technique which speeds up the operation a x b mod c a lot, but only for large numbers. If you want more details, feel free to check out Fedor’s code.

This ended up not being the tricky part of bringing it to the browser.

Finding Primes

Note Diffie-Hellman objects can be created in one of three ways:

1. Specify a MODP group

This is the easiest way: specify one of the MODP groups, which are predefined primes based on digits of Pi. The ones relevent to Diffie-Hellman are modp1 and modp2 defined here and modp5, modp14, modp15, modp16, modp17, modp18 defined here.

RFC 3526 includes a table of key lengths, and you’ll notice that the strength estimate does not scale with modulus bits. We will discuss this and alternatives in a later post on elliptical curves.

2. Specify a prime (and maybe a generator)

This method is pretty much what it says on the tin. OpenSSL checks that the prime is “safe,” and if it isn’t, in Node version 0.10 it throws an error. However, in later versions it just silently notes this in a property of the Diffie-Hellman object. The OpenSSL definition of a safe prime is not actually met by the MODP primes, meaning you can’t set them manually in Node 0.10.

3. Specify a prime length (and maybe a generator)

In this last method, we need to generate a new prime, which the user will need to send to the other party. This is where things get tricky, and is what took the longest. Our task is to:

generate a random number of the specified bit length that is prime and is “safe.”

What does “safe” mean?

To be a safe prime means that, for a prime p , then (p-1)/2 must also be prime. Note that since all primes besides 2 are odd, (p-1)/2 is the same as p >> 1 ( p right shifted by 1 bit). Then if the generator is 2, then p mod 24 must be 11 or if the generator is 5 then p mod 10 must be 3 or 7. At some point in the past, if the generator was 3 then p mod 12 needed to be 5, but this is no longer the case.

The algorithm I came up with (with help from Fedor) was as follows, to generate a safe prime of b bits:

Generate random buffer of Math.ceil(bits / 8) bytes. Make a big number p out of it. Then, while the bit length of p is larger than b , we shift it right a bit. So that both p and p>>1 are odd, we make sure the rightmost 2 bits are set. If the generator is 2, then we add 4 to it until p mod 24 is 11, or if the generator is 5, we add 4 until p mod 10 is 3. Calculate p>>1 and call it p2 . Test if p2 is prime. If not, go to 9. Test if p is prime. If so, return p . If the generator is 2, then we add 24 to p and 12 to p2 . If the generator is 5, then we alternate adding 4 to p and 2 to p2 while adding 16 to p and 8 to p2 . For all other generators, we add 4 to p and 2 to p2 . If the bits of p are greater then b , go back to 1. Otherwise, go back to 7.

You’ll note that we test p2 before we test p . This is because p2 is smaller than p , and we need to test a lot of candidates to find a safe prime, so we want to eliminate bad candidates as quickly as possible. This influences our prime testing methods, where we use three different tests, starting with one that is fast but prone to false positives and ending with a slower one that is less likely to incorrectly name something as prime.

Edit: after I wrote this, I realized it was more efficient to test p right after testing p2 for each test, not do all 3 tests for p2 then all 3 for p .

Method 1: Simple Sieve

This and the next method were taken from self-signed, and just involves generating (and for the love of God, caching) a list of small primes using the Sieve of Eratosthenes (I think). I just used Fedor’s default of all primes less then 0x100000 . (You can see the code to generate them here.) Then we go through this list and, for each prime pr , we see if our prime mod pr is 0.

The only modification I made to this for Diffie-Hellman was to also check if pr and the prime we were checking were the same number. If that’s the case, then our number is prime. Fedor could get away without checking, because all of his primes would need to be large, but I have to reproduce the OpenSSL API, which will give you a prime with as few as 15 bits.

Method 2: Fermat primality test

This isn’t too complex. Basically, for a prime p , choose a number a such that a is larger then 1 but smaller than p . a pow p-1 mod p should equal 1, unless p is quote “a Fermat liar.” We just check for a = 2 ; thats good enough because if any fall through the cracks…

Method 3: Miller Rabin

The Miller-Rabin primality test is similar to Fermat’s, but more complete. You can read the details on Wikipedia, but basically it is a heuristic that lets us say we are pretty sure that this number is prime, and the benefits of pretty sure outweigh the time it would take to calculate. For the size of primes that it is reasonable to be able to find in a browser, Miller Rabin is overkill most likely. (I have yet to see it knock out a prime that Fermat didn’t catch.)

Performance

The performance for generating primes is not great, based on some benchmarking.

The cost of testing a prime candidate scales at least linearly (possibly worse).

The number of candidate numbers to test to find a prime scales linearly.

The number of candidate primes to test to find a safe prime scales linearly too but…

The number of candidate numbers to test to find a safe prime scales exponentially.

So in other words, it gets real slow real fast. So don’t generate any primes in the browser in production.

Thanks to Nolan Lawson for proofreading this and Fedor Indutny for doing all the hard work. Join me next time for RSA signing.

Questions, comments and complaints can be directed at me on twitter.