In 1978, R. Rivest, A. Shamir, and L. Adelman published the description of the RSA public key system[RSA], which we will describe here. As discussed in §9.3, a public key system allows anyone to encode messages that only the intended recipient may decode. This algorithm relies on the fact that unless one knows some special facts about n, calculating the Euler -function (n) is as hard as factoring n, which, for very large n (say, 200 digits), is very hard indeed.
To set up the system, we pick at random two large primes p and q, of about 100 digits each. We then compute the base n as the product of p and q. Note that (n) = (p - 1)(q - 1). We also pick some other number e, called the exponent, which is relatively prime to (n). We make the numbers (n, e) public-- these form the key needed for encoding. We also use the Euclidean algorithm to compute x which satisfies ex - y(n) = 1; that is, so that ex(n) = 1. The number x is the part of the key we keep private; our decrypting key is the pair (n, x).
To encode a message: The sender divides his message up into blocks of length k, and to each block assigns an integer (for example, by treating the characters of the message as a k-graph, as in §5.3, with 0 < n. For each block of plaintext, the sender transmits n = .
To decode the message: For each unit of the crypttext received, the recipient computes n, which is the integer representing of a k-graph of the original plaintext message. We can see that this is so, because
This relies on Euler's theorem (Thm. 10.11), so that
n = 1.
Euler's theorem only guarantees that this will work for
gcd(, n) = 1.
However, we can easily see that decryption works for all < n,
whether or not it is relatively prime to n.
If gcd(, n) = 1, the result follows from Euler's theorem.
Otherwise, since p and q are prime, we have a multiple of either p or q, but not both (since < pq by assumption). Suppose 0 (mod p), and so
Remarks.
Now that we have covered how the RSA system works in theory, it is fairly straightforward to implement it.
First, we write a Maple procedure which, given a size for n (our public base), will choose n as the product of two large primes p and q, as well as an exponent e which is relatively prime to (n), and compute the decrypting exponent x.
For real security, n should be at least 200 decimal digits, meaning that the primes p and q will be about 100 digits each. Such large numbers are not easily factored (which is how we get our security), so how do we expect to be able to find 100-digit prime numbers? It turns out that there are a number of very efficient ways to test whether a number is prime without actually factoring it. Maple's isprime function uses such methods. So all we really need to do is generate a random number of around 100 digits, then use nextprime to get the next prime bigger than that number. We should use randomize() to ensure that we get different numbers each time (See the discussion of pseudo-random numbers in §5.2, page ).
In RSAsetup below, the approximate number of digits for n is given, and p is chosen as a prime with one less than half that many digits, while q has one more than half the number of digits of n. This means n must have at least 5 digits (or else p would have to be 2, 3, 5 or 7). After calculating n and (n), a random number is chosen for the public exponent e. If this number is not relatively prime to (n), another random choice is taken until such a number is found. Finally, x is computed as the inverse of e modulo (n). The result of the function is the public key (n, e) and the private key (n, x).
RSAsetup:=proc(numdigits::posint)
local p,q,phi,e,n, x, pm, qm;
if (numdigits <= 4) then
error("Too few digits for this to work");
fi;
pm:=floor((numdigits-1)/2);
qm:=numdigits-pm;
p:=nextprime(rand(10^(pm-1)..10^(pm))());
q:=nextprime(rand(10^(qm-1)..10^(qm))());
n:=p*q;
phi:=(p-1)*(q-1);
e:=rand(3..phi/2)();
while(gcd(e,phi) <> 1) do
e:=rand(3..phi/2)();
od;
x := modp(1/e, phi);
return([[n,e],[n,x]]);
end:
>
randomize():
We should remark that in certain cases, both p and q might be chosen at the extreme ends of their ranges, giving n with one fewer or one more digit than requested. If this were important, it could be repaired with a slightly more complicated program.
Once the keys are chosen, writing the heart of the RSA encoding is
quite easy. We just need a function that eats a number and a
key (n, e), and computes
n.
>
RSAEncodeNum := proc(num::nonnegint, key::list(posint))
return( modp( num &^ key[2], key[1] ));
end:
As a brief example, we'll generate a random pair of keys with a 5-digit modulus, and then encrypt and decrypt the number 47.
>
keys:=RSAsetup(5):
public := keys[1];
private:= keys[2];
>
RSAEncodeNum(47,public);
RSAEncodeNum(
Of course, we want to encrypt more than a single number, but this is the heart of the whole thing.
In order to encrypt a string of text, we have to convert our text to a list of numbers. It is probably best to use k-graphs to represent our text, as in §5.3, where k is the largest integer power of the size of our alphabet that is smaller than n. While it will work to use a single character alphabet, this can compromise security significantly-- see the remarks at the end of §11.1.
We can use StringToKgraph and KgraphToString from §5.3 to represent our strings as k-graphs. In order to determine the appropriate choice of k, we find the largest integer k so that
Once we have our plaintext represented as a list of integers, we merely map the function RSAEncodeNum onto that list.
>
Alphabet := cat("",Select(IsPrintable,convert([seq(i,i=1..255)],bytes))):
Alen := length(Alphabet);
k:=floor(log[Alen](public[1]));
>
text := "Once upon a midnight dreary, while I pondered, weak and weary":
>
StringToKgraph(text,k);
>
crypt:=map(RSAEncodeNum,
We can reverse the process, using the private key, to decrypt.
>
KgraphToString(
map(RSAEncodeNum,crypt,private),
k);
It is important to note that if we want to represent the result of the
encryption as a string instead of a list of numbers, we must use
(k + 1)-graphs rather than k-graphs. This is because
Alenk < n, so there will often be so that
n > Alenk. In the example above, note that
972 = 9409, and there are several elements of the list
crypt larger than 9409.
We can put it all together in two simple procedures.
Alen := length(Alphabet);
k:=floor(log[Alen](key[1]));
KgraphToString(
map(RSAEncodeNum,
StringToKgraph(text, k),
key),
k+1);
end:
RSADecodeString := proc(text::string, key::list(posint))
local Alen, k;
global Alphabet;
Alen := length(Alphabet);
k:=floor(log[Alen](key[1]));
KgraphToString(
map(RSAEncodeNum,
StringToKgraph(text, k+1),
key),
k);
end:
>
RSAEncodeString := proc(text::string, key::list(posint))
local Alen, k;
global Alphabet;
Now we try it out, with a 150-digit choice of n. Note that this means we'll be using 75-graphs, since 9775 < 10150 < 9776.
>
keys:=RSAsetup(150):
public:=keys[1]; private:=keys[2];
>
crypt:=RSAEncodeString(text,public);
>
RSADecodeString(crypt,private);