RSA with small numbers

Introduction

I was inspired to do this demo by a marvellous essay on RSA encryption on www.muppetlabs.com. The RSA keys used by your browser are 1024-bit numbers, which are a little hard to check by hand.  This demo allows you to create your own RSA key pair using any prime you like up to 241. This makes it much easier to see what's going on, but I wouldn't rely on it to keep your credit card number safe.  (I know that IE's Help About says "Cipher Strength 128-bit".  This refers to the 128-bit symmetric encryption keys used for SSL sessions ('symmetric' means that the decryption process is just the reverse of the encryption process).  But 1024-bit RSA keys are used for the exchange of the session keys between client and server.  A good 1024-bit RSA key is thought to be about as secure as a 128-bit symmetric key but it is much slower, so it is used just to kickstart the symmetric session).

Click here to run the demo (requires .NET CLR).

Click here to download all the source code (rsa.zip - 10 KB)

The C# files:

Using the demo

To create a key pair, fire up the demo and enter two primes p and q . 7 and 29 are a good example.  The demo starts by displaying n (the product of the two primes) and Φ(n) (the number of integers between 1 and n-1 that have no factors in common with n).  Even I can prove that if p and q are primes then
Φ(n) = (p-1) * (q-1), so it's easy for us to compute even though it is the great secret at the heart of the code. 
Now the demo tries to find d and e such that de = cΦ(n) + 1 for some integer c (with a couple of other restrictions).  When it finds these, you've got yourself an RSA public and private key pair.  For the primes 7 and 29 the demo will give you the key pair private (n=203, d=5) and public (n=203, e=101)

The next step is to use it.  The first thing to recognise when composing a message is that you can't encrypt just any number.  You can only encrypt a number between 2 and n-2 that has no factors in common with n.  In real life, you could ensure this by encrypting only numbers less than the smaller of p and q; since p and q would be 512 bits long there would be no shortage of messages.  The owner of the key could publish an upper bound for messages that would give no clue about p and q.

The second point is that you can encrypt the message using either the public or the private key, as long as you make sure that you use the other key to do the decryption!  Encrypting with the public key is what someone does when they want to send the message to you.  Encrypting with the private key is what you do when you want to digitally sign the message.

Enter your 'message' number and click either the 'Encrypt using public key' or 'Encrypt using private key' button.  Using our example, the encryption of the number 6 using the private key is:

65 mod 203 = 62

And the encryption of 62 using the public key is:

62101 mod 203 = 6   - back to the original message!

The last section of the demo shows the complete public and private encipherments for your key pair.  The key is used to encipher each integer from 2 to n-2, excluding those that are multiples of p or q.  Two features to notice are:

    • If a is enciphered as b by the public key, then b will be enciphered as a by the private key, and vice versa
    • Every number in the table is enciphered to another number in the table (sometimes a number may be enciphered to itself)

Note: An RSA key is safe only if it is impractical to factorise n into p and q, because p and q reveal Φ(n) which, combined with e, reveals the secret number d.  This is why in real life the primes need to be so big.  Also, when creating a real RSA key you need to be a bit careful in your prime number selection.  Some big primes will produce an n that is vulnerable to factorisation tricks.  Please see mathworld.wolfram.com/RSAEncryption.html  for details.

The modpower function

In order to do encryption and decryption with our keys we need a function that computes ab mod c. The trouble is that ab will be way too big for a 32-bit integer type, even though for our purpose a is less than c. So we need a way to break it down.

The first thing to recognise is that xy mod c = ( x mod c )( y mod c ) mod c.
For example, consider x = 17, y = 8, c = 6:

xy mod c = 136 mod 6 = 4

x mod c = 17 mod 6 = 5

y mod c = 8 mod 6 = 2

( x mod c )( y mod c ) mod c = 5 * 2 mod 6 = 10 mod 6 = 4

Now, let's break b down into its binary representation.  For example, consider b = 23:

23 = 16 + 4 + 2 + 1 = 24 + 22 + 21 + 20 (ie the binary representation is 101112)

So a23 = a2^4 + 2^2 + 2^1 + 2^0 = a2^4.a2^2. a2^1. a2^0

Thus a23 mod c = a2^4.a2^2. a2^1. a2^0 mod c

= a2^4 mod c . a2^2 mod c . a2^1 mod c . a mod c

This allows us to deal with each 'bit' separately.  To handle bit 2, for example, we must compute a2^2 mod c. We can also see that we can ignore the bits that are zero. 

Now a2^2 = (a2)2

Once again we can break this down, because ab^d mod c = ((ab mod c) d) mod c, so

(a2)2 mod c = ((a2 mod c)2) mod c

We can handle this as long as we can handle the square of any number < c.  (Incidentally, for 32-bit arithmetic this limits c to 65,536. Since c in our case is the product of two primes that should be of similar magnitude to each other, our primes will be ≈ 256.  The demo enforces a limit of 241 on each prime to ensure that we don't get any overflows).

 Now look at a2^3 mod c = ((a2)2) 2 mod c.

This can be rewritten as (((a2 mod c)2) mod c) 2 mod c

This includes the term ((a2 mod c)2) mod c that we already calculated when handling (a2)2, and we can see a pattern emerging:

  • To handle the 0th bit, we compute (a mod c).  Call this residual0
  • To handle the 1st bit, we compute (residual02 mod c). Call this residual1
  • To handle the 2nd bit, we compute (residual12 mod c). Call this residual2
  • To handle the nth bit, we compute (residualn-12 mod c).  Call this residualn

 Then ab mod c = residuali . residualj . residualk ... mod c,

where residuali , residualj , etc are the residuals for the non-zero bits of b. Computing the residuals is an O(log2 b) operation, and multiplying them together is another O(log2 b) operation.

©2009 Carl Johansen