knapsack_sol.mw

MCS 320 Project One due Monday 26 September 2005 at 2PM

> restart;

This worksheet contains a possible solution to the project.

Assignment One: make your own worksheet model

Using the given message

> message := "mcs 320 is fun":

we will walk through the three steps in the process:

 1. encode/decode the message, converting a string into a sequence of bits;

 2. encrypt the sequence of bits as a subset sum problem;

 3. decrypt the sequence using the private key.

1. Encode/decode a string of characters as a sequence of bits

We use the following function which takes on input a string and returns a sequence of bytes, each byte is a sequence of at most 8 bits:

> encode := s -> map(x->convert(x,base,2),convert(s,bytes)):

> encoded_message := encode(message);

encoded_message := [[1, 0, 1, 1, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 1, 1, 1], [0, 0, 0, 0, 0, 1], [1, 1, 0, 0, 1, 1], [0, 1, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1], [1, 0, 0, 1...encoded_message := [[1, 0, 1, 1, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 1, 1, 1], [0, 0, 0, 0, 0, 1], [1, 1, 0, 0, 1, 1], [0, 1, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1], [1, 0, 0, 1...encoded_message := [[1, 0, 1, 1, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [1, 1, 0, 0, 1, 1, 1], [0, 0, 0, 0, 0, 1], [1, 1, 0, 0, 1, 1], [0, 1, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1], [1, 0, 0, 1...

Our message is now encoded as a list of bit sequences.

The opposite of encode is decode:

> evalbase2 := x -> sum(x[i]*2^(i-1),i=1..nops(x)):

> decode := s -> convert(map(x->evalbase2(x),s),bytes):

> decode(encoded_message);

2. Pack the bytes as a subset sum problem

The knapsack cryptosystem is a public key cryptosystem.  The public key is a sequence of weights, generated from a sequence of super increasing weights.  A sequence of weights is super increasing if every element in the sequence is larger than the sum of all previous elements in the sequence.  For example:

> w := [2,3,6,13,27,52,105,220]:  # a super increasing sequence of weights

But this super increasing sequence of weights is secret.  The public key is generated from two relatively prime numbers u and v, which must be higher than the sum of the weights.  If we pick two prime numbers, then gcd(u,v) = 1.  This requirement is needed to be able to divide by v, modulo u.

> bound := add(w[i],i=1..nops(w)):

> u := nextprime(bound): v := nextprime(234):

> public_key := map(t->v*t mod u,w);

public_key := [47, 286, 141, 90, 419, 360, 97, 429]

To encrypt the list of bit sequences, we multiply the bits with the weights, as we would pack a knapsack.  We pick the elements of weights w[i] for which the corresponding bit equals 1.  Every byte is now represented by the sum of the weights selected in the knapsack.  Our message is thus encrypted as a list of sums.  We use the following functions:

> pack_byte := (w,x) -> sum(w['i']*x['i'],'i'=1..min(nops(w),nops(x))):

> pack_bytes := (w,x) -> map(t->pack_byte(w,t),x):

> encrypt := (key,s) -> pack_bytes(key,encode(s)):

> encrypted_message := encrypt(public_key,message);

encrypted_message := [735, 790, 1209, 360, 1112, 1065, 779, 360, 594, 1209, 360, 884, 1064, 974]

The message is encrypted with the public key of the intended recipient.

3. Decrypting is easy for super increasing weights

The intended recipient of the message knows u,v, and w.  Since w is super increasing, solving the subset sum problem is as easy as computing the binary decomposition of a number.

> unpack_byte := proc(w,s)
 description `solves the subset sum problem for super increasing w`:

 local n,i,x,r:

 x := []:

 n := nops(w):

 r := s:

 for i from n by -1 to 1 do

   if r >= w[i] then

     x := [1,op(x)]:

     r := r-w[i]:

   elif nops(x) > 0 then

     x := [0,op(x)]:

   end if:

 end do:

 return x:

end proc:

> unpack_bytes := (w,x) -> map(t->unpack_byte(w,t),x):

> private_key := 1/v mod u:

> decrypt := (key,w,s) -> decode(unpack_bytes(w,map(t->key*t mod u,s))):

> decrypt(private_key,w,encrypted_message);

Assignment Two: build your own cryptosystem

The cryptosystem consists of a super increasing sequence of weights, which is turned into a general sequence using u and v.

> my_w := [1,2,4,8,16,32,64,128]:

> my_bound := add(w[i],i=1..nops(w)):

> my_u := nextprime(my_bound); my_v := nextprime(313);

my_u := 431

my_v := 317

> my_public_key := map(t->my_v*t mod my_u,my_w);

my_public_key := [317, 203, 406, 381, 331, 231, 31, 62]

> my_private_key := 1/my_v mod my_u;

my_private_key := 155

> my_message := "Symbolic computation is about computing with symbols.\n
Like numerical computations may suffer from roundoff and ill conditioning,\n

we must be careful about false simplifications and expression swell.";

my_message :=

> my_encrypted_message := encrypt(my_public_key,my_message);

my_encrypted_message := [882, 1291, 1366, 465, 1569, 1049, 960, 782, 231, 782, 1569, 1366, 593, 1316, 999, 579, 999, 960, 1569, 1252, 231, 960, 1113, 231, 579, 465, 1569, 1316, 999, 231, 782, 1569, 13...my_encrypted_message := [882, 1291, 1366, 465, 1569, 1049, 960, 782, 231, 782, 1569, 1366, 593, 1316, 999, 579, 999, 960, 1569, 1252, 231, 960, 1113, 231, 579, 465, 1569, 1316, 999, 231, 782, 1569, 13...my_encrypted_message := [882, 1291, 1366, 465, 1569, 1049, 960, 782, 231, 782, 1569, 1366, 593, 1316, 999, 579, 999, 960, 1569, 1252, 231, 960, 1113, 231, 579, 465, 1569, 1316, 999, 231, 782, 1569, 13...my_encrypted_message := [882, 1291, 1366, 465, 1569, 1049, 960, 782, 231, 782, 1569, 1366, 593, 1316, 999, 579, 999, 960, 1569, 1252, 231, 960, 1113, 231, 579, 465, 1569, 1316, 999, 231, 782, 1569, 13...my_encrypted_message := [882, 1291, 1366, 465, 1569, 1049, 960, 782, 231, 782, 1569, 1366, 593, 1316, 999, 579, 999, 960, 1569, 1252, 231, 960, 1113, 231, 579, 465, 1569, 1316, 999, 231, 782, 1569, 13...my_encrypted_message := [882, 1291, 1366, 465, 1569, 1049, 960, 782, 231, 782, 1569, 1366, 593, 1316, 999, 579, 999, 960, 1569, 1252, 231, 960, 1113, 231, 579, 465, 1569, 1316, 999, 231, 782, 1569, 13...my_encrypted_message := [882, 1291, 1366, 465, 1569, 1049, 960, 782, 231, 782, 1569, 1366, 593, 1316, 999, 579, 999, 960, 1569, 1252, 231, 960, 1113, 231, 579, 465, 1569, 1316, 999, 231, 782, 1569, 13...my_encrypted_message := [882, 1291, 1366, 465, 1569, 1049, 960, 782, 231, 782, 1569, 1366, 593, 1316, 999, 579, 999, 960, 1569, 1252, 231, 960, 1113, 231, 579, 465, 1569, 1316, 999, 231, 782, 1569, 13...my_encrypted_message := [882, 1291, 1366, 465, 1569, 1049, 960, 782, 231, 782, 1569, 1366, 593, 1316, 999, 579, 999, 960, 1569, 1252, 231, 960, 1113, 231, 579, 465, 1569, 1316, 999, 231, 782, 1569, 13...

> decrypt(my_private_key,my_w,my_encrypted_message);

Assignment Three: small numbers are insecure

The purpose of this assignement is to show that with the LLL algorithm we can decrypt a large portion of the message, if small numbers are chosen.  For larger numbers, this is no longer the case.

> with(IntegerRelations,LLL):

We will use this auxiliary function, which returns true if r is a bit sequence, false otherwise.

> is_byte := proc(r)
 local i:

 for i from 1 to nops(r) do

   if r[i] < 0 or r[i] > 1

    then return false;

   end if;

 end do;

 return true;

end proc:

The use of the LLL to decrypt one character is done by the following procedure:

> crack := proc(public_key,s)
 local id,v,A,B,r,c,i:

 id := matrix(9,8,(i,j) -> piecewise(i=j,1,0)):

 v := matrix(9,1,[-op(public_key),s]):

 A := linalg[augment](id,v):

 B := [seq(convert(linalg[row](A,i),list),i=1..9)]:

 r := LLL(B,'integer');

 for i from 1 to nops(r) do

   if is_byte(r[i])

    then return decode([r[i]]):

   end if;

 end do:

 return "":

end proc:

and applied to a encrypted message as follows:

> map(t->crack(my_public_key,t),my_encrypted_message);

[[[[[[[

We see that except for the "y", the LLL algorithm is very successful in decrypting the message.

We now choose numbers as large as 12 digits to generate our public key:

> randomize(1):

> new_u := nextprime(rand()); new_v := nextprime(rand()); new_public_key := map(t->new_v*t mod new_u,my_w); new_private_key := 1/new_v mod new_u;

new_u := 427419669163

new_v := 321110693273

new_public_key := [321110693273, 214801717383, 2183765603, 4367531206, 8735062412, 17470124824, 34940249648, 69880499296]

new_private_key := 127832151371

And encrypt our message with these larger numbers:

> new_encrypted_message := encrypt(new_public_key,my_message);

new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...new_encrypted_message := [579587722716, 386623661363, 380072364554, 267212091855, 594874081937, 58961671281, 377888598951, 588322785128, 17470124824, 588322785128, 594874081937, 380072364554, 61145436...

> map(t->crack(new_public_key,t),new_encrypted_message);

[[[[[[

Now we see that the attack by LLL is no longer successful.

Assignment Four: little public keys are insecure

The public key is the vector of weights.  With a public key of size 8, one could try all possible 256 = 2^8 combinations to decrypt one character.  Trying one combination takes at most 7 additions.  Assume that trying 7 additions takes only one millisecond, then it takes 256 milliseconds to decrypt one character.

> one_char := .256*seconds;

one_char := .256*seconds

> length(my_message)*one_char;

51.456*seconds

So it would take less than a minute to decrypt my message.

Suppose our public key would be twice as long, i.e.: 16:

> one_char := evalf(2^16/1000)*seconds;

one_char := 65.53600000*seconds

> a := length(my_message)*one_char;

a := 13172.73600*seconds

> op(1,a)/(60*60)*hours;

3.659093333*hours

Decrypting our message would take longer, but not perhaps as long as we would like.  Let us take 64 as size of the public key:

> one_char := evalf(2^64/1000)*seconds;

one_char := 0.1844674407e17*seconds

> op(1,one_char)/(60*60*24*365)*years;

584942417.2*years

>

Even decrypting as little as one character would take more than a million years.

To use a public key with 64 bits, the encode and decode operations would need to be changed.  Instead of working with lists of 8 bits, we would need to encode our strings as lists of 64 bits each.