PseudoRandom.mws

Pseudo random strings

©Mike May, S.J., 2002, maymk@slu.edu

>   

Preliminaries

The book talks of using a pseudo random string as a means of encryption.  To be able to to use this technique we first need some preliminaries.  In particular we need procedures for XOR on a pair of bits.  That procedure is used to XOR bytes.  The procedure parses or reads a 0 or 1 string as a number, add mod 2, then convert back to a string.

>    bitXOR := (bit1,bit2) -> convert(((parse(bit1)+parse(bit2)) mod 2),string):
byteXOR := (byte1,byte2) ->
          cat(seq(bitXOR(substring(byte1,i), substring(byte2,i)), i=1..8)):

>    byteXOR("01001101", "01100110");

We also want a number of procedures to convert between data types.  The messages will start out as ASCII characters.  To use an XOR procedure we will want each character to be represented as a byte string composed of eight bits, with leading zeroes printed.  To store ciphertext we will want to use two character hex strings.  Thus we need procedures to go from character to binary string to hex string.

>    charToBinByte := proc(character)
   local binChar:
   binChar := convert(op(convert(character, bytes))+256,binary):
   substring(convert(binChar,string),2..9):
end:

>    binToHexByte := binString ->
   substring(convert(convert(parse(binString),decimal,binary)+256,hex),2..3):

>    s1 := charToBinByte("\n");

s1 :=

>    h1 := binToHexByte(s1);

h1 := `0A`

We also want procedures to convert back.

>    hexToBinByte := hexString ->
   substring(convert(convert(convert(hexString, decimal,hex)+256,binary),
       string),2..9):

>    binByteToChar := binString ->
        convert([convert(binString,decimal,binary)],bytes):

>    s2 := hexToBinByte(h1);

s2 :=

>    binByteToChar(s2);


We also need routines from the conversion worksheet that can arrange the hex strings into words and break the words back into pieces.

>    blocker := proc(hexvec,bsize)
local numCharacters, paddingNum, numWords, i, j, temp, hexblock;
temp := hexvec;
numCharacters :=linalg[vectdim](hexvec);
numWords := ceil(numCharacters/bsize);
paddingNum := numWords*bsize - numCharacters;
temp := [op(temp),seq("00",j = 1..paddingNum)];
hexblock := array(1..numWords,
   [seq( cat(seq(temp[bsize*i + j], j = 1..bsize)), i=0..numWords-1)]):
convert(hexblock,list):
end:

>    unblocker := (hexblock,bsize) ->
   map(hexString -> seq(substring(hexString,2*j-1..2*j), j = 1..bsize),
      hexblock):

Setting up Blum-Blum-Shub

To set up the Blum-Blum-Shub method of producing a pseudorandom string, we first need to produce a pair of primes.  We want primes congruent to 3 mod 4.  Since we want to harvest 4 bits at each step we would like the primes to be about 1000.

>    p := nextprime(rand(1000..10000)());
while ((p mod 4) = 1) do p := nextprime(p) od;
q := nextprime(rand(1000..10000)());
while ((q mod 4) = 1) do q := nextprime(q) od;
n := p*q;

p := 2287

q := 1277

q := 1279

n := 2925073

To make this more predictable we can fix p and q.

>    p := 5323;
q := 7411;
n := p*q;

p := 5323

q := 7411

n := 39448753

Next we need a seed.  The seed should be relatively prime to n.

>    x0:= rand(n)();
while (igcd(x0,n)>1) do x:= rand(n)() od;

x0 := 34435067

Once again, we then fix the seed so that we can get the same result on repeated runnings of the worksheet.

>    x0 := 8288583;

x0 := 8288583

Now we produce a procedure that takes a key, runs two rounds of the procedure to produce a key byte and a new seed to use in the next round..

>    nextKeyByte := proc(modulus, key)
   local stream, x1, x2:
   x1 := Power(key,2) mod modulus:
   stream := convert(x1 mod 16, hex):
   x2 := Power(x1, 2) mod modulus:
   stream := cat(stream, convert(x2 mod 16, hex)):
   [x2, hexToBinByte(stream)];
end:

>    oneStep := nextKeyByte(n,x0);

oneStep := [28235967,

>    oneStep := nextKeyByte(n,oneStep[1]);

oneStep := [3285810,

This lets us produce a string of bytes to use as a key.

>    x1 := x0:
for i from 1 to 20 do
nextStep := nextKeyByte(n,x1):
x1 := nextStep[1]:
print(i, nextStep[2]);
od:

1,

2,

3,

4,

5,

6,

7,

8,

9,

10,

11,

12,

13,

14,

15,

16,

17,

18,

19,

20,

>   

Encryption and Decryption with BBS

We are ready to use this to encode a message.  First we create a message to encode:

>    mess1 := "AZaz Good morning Mr. Phelps.  Your job today, should you choose
to accept it, is to learn to use Blum-Blum-Shub encryption. AZaz";

mess1 :=
mess1 :=

>    messlength := length(mess1);

messlength := 129

We encrypt the message and write the code as words of 8 hex characters corresponding to 4 ASCII characters.

>    x1 := x0:
cipher1 := []:
for i from 1 to messlength do
  oneStep := nextKeyByte(n,x1):
  oneByte := byteXOR(oneStep[2], charToBinByte(substring(mess1,i))):
  hexByte := binToHexByte(oneByte):
  x1 := oneStep[1]:
  cipher1 := [op(cipher1), hexByte]:
od:
cipher1;

[`2E`, `38`, `93`, B6, `4D`, B6, CB, `56`, `99`, `7A`, A7, `40`, `1D`, B1, `55`, `3A`, AE, FB, B7, EC, `48`, `92`, A8, B8, C9, F4, ED, BF, CB, `6B`, `61`, `86`, `08`, BE, ED, `00`, D9, A1, `69`, `5F`, ...
[`2E`, `38`, `93`, B6, `4D`, B6, CB, `56`, `99`, `7A`, A7, `40`, `1D`, B1, `55`, `3A`, AE, FB, B7, EC, `48`, `92`, A8, B8, C9, F4, ED, BF, CB, `6B`, `61`, `86`, `08`, BE, ED, `00`, D9, A1, `69`, `5F`, ...
[`2E`, `38`, `93`, B6, `4D`, B6, CB, `56`, `99`, `7A`, A7, `40`, `1D`, B1, `55`, `3A`, AE, FB, B7, EC, `48`, `92`, A8, B8, C9, F4, ED, BF, CB, `6B`, `61`, `86`, `08`, BE, ED, `00`, D9, A1, `69`, `5F`, ...
[`2E`, `38`, `93`, B6, `4D`, B6, CB, `56`, `99`, `7A`, A7, `40`, `1D`, B1, `55`, `3A`, AE, FB, B7, EC, `48`, `92`, A8, B8, C9, F4, ED, BF, CB, `6B`, `61`, `86`, `08`, BE, ED, `00`, D9, A1, `69`, `5F`, ...

>    code := blocker(cipher1,4);

code := [`2E3893B6`, `4DB6CB56`, `997AA740`, `1DB1553A`, AEFBB7EC, `4892A8B8`, C9F4EDBF, CB6B6186, `08BEED00`, D9A1695F, D5449163, `4BFA97FA`, `672FCF98`, `442959FA`, `80BA9908`, `4DF781EF`, `25132894`...
code := [`2E3893B6`, `4DB6CB56`, `997AA740`, `1DB1553A`, AEFBB7EC, `4892A8B8`, C9F4EDBF, CB6B6186, `08BEED00`, D9A1695F, D5449163, `4BFA97FA`, `672FCF98`, `442959FA`, `80BA9908`, `4DF781EF`, `25132894`...
code := [`2E3893B6`, `4DB6CB56`, `997AA740`, `1DB1553A`, AEFBB7EC, `4892A8B8`, C9F4EDBF, CB6B6186, `08BEED00`, D9A1695F, D5449163, `4BFA97FA`, `672FCF98`, `442959FA`, `80BA9908`, `4DF781EF`, `25132894`...

To decrypt we reverse the process.

>    cipher2 := unblocker(code,4);

cipher2 := [`2E`, `38`, `93`, B6, `4D`, B6, CB, `56`, `99`, `7A`, A7, `40`, `1D`, B1, `55`, `3A`, AE, FB, B7, EC, `48`, `92`, A8, B8, C9, F4, ED, BF, CB, `6B`, `61`, `86`, `08`, BE, ED, `00`, D9, A1, `...
cipher2 := [`2E`, `38`, `93`, B6, `4D`, B6, CB, `56`, `99`, `7A`, A7, `40`, `1D`, B1, `55`, `3A`, AE, FB, B7, EC, `48`, `92`, A8, B8, C9, F4, ED, BF, CB, `6B`, `61`, `86`, `08`, BE, ED, `00`, D9, A1, `...
cipher2 := [`2E`, `38`, `93`, B6, `4D`, B6, CB, `56`, `99`, `7A`, A7, `40`, `1D`, B1, `55`, `3A`, AE, FB, B7, EC, `48`, `92`, A8, B8, C9, F4, ED, BF, CB, `6B`, `61`, `86`, `08`, BE, ED, `00`, D9, A1, `...
cipher2 := [`2E`, `38`, `93`, B6, `4D`, B6, CB, `56`, `99`, `7A`, A7, `40`, `1D`, B1, `55`, `3A`, AE, FB, B7, EC, `48`, `92`, A8, B8, C9, F4, ED, BF, CB, `6B`, `61`, `86`, `08`, BE, ED, `00`, D9, A1, `...

>    x1 := x0:
decipher1 := []:
for i from 1 to messlength do
  oneStep := nextKeyByte(n,x1):
  oneByte := byteXOR(oneStep[2], hexToBinByte(cipher2[i])):
  oneChar := binByteToChar(oneByte):
  x1 := oneStep[1]:
  decipher1 := [op(decipher1), oneChar]:
od:
decipher1;

[
[
[
[

>    cat(op(decipher1));


Since we would like to be able to do this with arbitrary messages, we produce procedures for encryption and decryption.  The encryption procedure should take a message, a modulus (the n value), an initial key (the x0 value) and the number of ASCII characters in a code word.

>    BBSEncrypter := proc(message, modulus, key1, blocksize)
   local messlength, x1, cipher1, oneStep, oneByte, hexByte, i:
   messlength := length(message);
   x1 := key1:
   cipher1 := []:
   for i from 1 to messlength do
      oneStep := nextKeyByte(modulus,x1):
      oneByte := byteXOR(oneStep[2], charToBinByte(substring(message,i))):
      hexByte := binToHexByte(oneByte):
      x1 := oneStep[1]:
      cipher1 := [op(cipher1), hexByte]:
   od:
   blocker(cipher1,blocksize);
end:

>    cipherText := BBSEncrypter(mess1,n,x0,4);

cipherText := [`2E3893B6`, `4DB6CB56`, `997AA740`, `1DB1553A`, AEFBB7EC, `4892A8B8`, C9F4EDBF, CB6B6186, `08BEED00`, D9A1695F, D5449163, `4BFA97FA`, `672FCF98`, `442959FA`, `80BA9908`, `4DF781EF`, `251...
cipherText := [`2E3893B6`, `4DB6CB56`, `997AA740`, `1DB1553A`, AEFBB7EC, `4892A8B8`, C9F4EDBF, CB6B6186, `08BEED00`, D9A1695F, D5449163, `4BFA97FA`, `672FCF98`, `442959FA`, `80BA9908`, `4DF781EF`, `251...
cipherText := [`2E3893B6`, `4DB6CB56`, `997AA740`, `1DB1553A`, AEFBB7EC, `4892A8B8`, C9F4EDBF, CB6B6186, `08BEED00`, D9A1695F, D5449163, `4BFA97FA`, `672FCF98`, `442959FA`, `80BA9908`, `4DF781EF`, `251...

The decryption routine needs the same set of information with the plaintext replaced by ciphertext.

>    BBSDecrypter := proc(codeText, modulus, key1, blocksize)
   local cipher2, messlength, x1, oneStep, oneByte, oneChar, decipher1, i;
   cipher2 := unblocker(codeText,blocksize):
   messlength := nops(cipher2):
   x1 := key1:
   decipher1 := []:
   for i from 1 to messlength do
      oneStep := nextKeyByte(modulus,x1):
      oneByte := byteXOR(oneStep[2], hexToBinByte(cipher2[i])):
      oneChar := binByteToChar(oneByte):
      x1 := oneStep[1]:
      decipher1 := [op(decipher1), oneChar]:
   od:
   decipher1;
   cat(op(decipher1));
end:

>    BBSDecrypter(cipherText,n,x0,4);


Note that the extra characters correspond to filling out the last code word with spaces.  This is one reason to have an end of message string.

Exercise:

1) Use BBS to encrypt a message and post it to the bulletin board.  You need to post the encrypted message, the modulus, and the initial key.  Respond to someone else's message.

>   

Using Linear Feedback Shift Registers

To set up a linear feedback shift register we start with the polynomial that corresponds to a recurrence relation defining the linear feedback.

>    p1 := x^8 + x^4 +x^3 + x^2 + 1:

We can turn it into a matrix that works on a block of bits, shifting then up one place and using the linear feedback for the last bit.  To do that we want the transpose of the companion matrix.

>    m1 := linalg[transpose](linalg[companion](p1,x));

m1 := matrix([[0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0, 1], [-1, ...

If we now take the nth power of the matrix we have one that replaces a byte of bits with the next byte.

>    shiftMat := map(modp, evalm(m1^8),2);

shiftMat := matrix([[1, 0, 1, 1, 1, 0, 0, 0], [0, 1, 0, 1, 1, 1, 0, 0], [0, 0, 1, 0, 1, 1, 1, 0], [0, 0, 0, 1, 0, 1, 1, 1], [1, 0, 1, 1, 0, 0, 1, 1], [1, 1, 1, 0, 0, 0, 0, 1], [1, 1, 0, 0, 1, 0, 0, 0],...

If we start with 8 bits of the key string, we get the next 8 bits by multiplying by the matrix mod 2.

>    keyVect := vector([1,1,0,1,0,1,1,0]);
v2 := map(modp, evalm(shiftMat&*keyVect),2);

keyVect := vector([1, 1, 0, 1, 0, 1, 1, 0])

v2 := vector([0, 1, 0, 1, 1, 0, 0, 0])

We want a procedure to do this automatically.

>    shiftWord := (mat, vect) -> map(modp, evalm(mat&*vect),2);

shiftWord := proc (mat, vect) options operator, arrow; map(modp,evalm(`&*`(mat,vect)),2) end proc

This lets us produce a bit string that would continue for 255*8 bits without repeating.

>    v2 := keyVect;
for i from 1 to 10 do
v2 := shiftWord(shiftMat, v2);
od;

v2 := keyVect

v2 := vector([0, 1, 0, 1, 1, 0, 0, 0])

v2 := vector([0, 1, 1, 1, 1, 1, 0, 1])

v2 := vector([1, 0, 1, 1, 1, 1, 0, 1])

v2 := vector([0, 1, 1, 1, 0, 1, 0, 0])

v2 := vector([0, 1, 0, 0, 0, 0, 1, 1])

v2 := vector([0, 1, 1, 0, 0, 0, 1, 1])

v2 := vector([1, 1, 0, 0, 1, 1, 1, 0])

v2 := vector([0, 1, 1, 0, 0, 0, 1, 0])

v2 := vector([1, 1, 0, 1, 0, 0, 1, 0])

v2 := vector([0, 0, 1, 0, 1, 0, 0, 1])

We also want procedures to convert between a vector of bits and the string formed by concatenating the elements of the vector.

>    binByteToVector := binByte ->
    vector([seq(parse(substring(binByte,i)), i=1..8)]);
vectorToBinByte := vect ->
    cat(seq(convert(v1[i],string), i=1..8));

binByteToVector := proc (binByte) options operator, arrow; vector([seq(parse(substring(binByte,i)),i = 1 .. 8)]) end proc

vectorToBinByte := proc (vect) options operator, arrow; cat(seq(convert(v1[i],string),i = 1 .. 8)) end proc

We are ready to use this stream to encrypt a message.  Let's repeat the message from above.

>    mess1;
messlength := length(mess1);


messlength := 129

>    v1:= keyVect:
cipher1 := []:
for i from 1 to messlength do
  oneStep := vectorToBinByte(v1):
  oneByte := byteXOR(oneStep, charToBinByte(substring(mess1,i))):
  hexByte := binToHexByte(oneByte):
  v1 := shiftWord(shiftMat,v1):
  cipher1 := [op(cipher1), hexByte]:
od:
cipher1;
code := blocker(cipher1,4);

[`97`, `02`, `1C`, C7, `54`, `04`, `0C`, A1, `06`, F2, `44`, `3C`, C9, `53`, `93`, `08`, C4, `27`, `18`, `80`, AA, DF, `5B`, AE, E5, E2, `55`, B3, E7, `17`, `00`, F4, C3, C5, `89`, `5A`, `82`, E9, A5, ...
[`97`, `02`, `1C`, C7, `54`, `04`, `0C`, A1, `06`, F2, `44`, `3C`, C9, `53`, `93`, `08`, C4, `27`, `18`, `80`, AA, DF, `5B`, AE, E5, E2, `55`, B3, E7, `17`, `00`, F4, C3, C5, `89`, `5A`, `82`, E9, A5, ...
[`97`, `02`, `1C`, C7, `54`, `04`, `0C`, A1, `06`, F2, `44`, `3C`, C9, `53`, `93`, `08`, C4, `27`, `18`, `80`, AA, DF, `5B`, AE, E5, E2, `55`, B3, E7, `17`, `00`, F4, C3, C5, `89`, `5A`, `82`, E9, A5, ...
[`97`, `02`, `1C`, C7, `54`, `04`, `0C`, A1, `06`, F2, `44`, `3C`, C9, `53`, `93`, `08`, C4, `27`, `18`, `80`, AA, DF, `5B`, AE, E5, E2, `55`, B3, E7, `17`, `00`, F4, C3, C5, `89`, `5A`, `82`, E9, A5, ...

code := [`97021CC7`, `54040CA1`, `06F2443C`, C9539308, C4271880, AADF5BAE, E5E255B3, E71700F4, C3C5895A, `82E9A5BC`, B1CB36C6, `0F57D4BE`, `2E61DE89`, `6DDE6EE2`, `743C28E9`, FD01323E, `531599D5`, B06E...
code := [`97021CC7`, `54040CA1`, `06F2443C`, C9539308, C4271880, AADF5BAE, E5E255B3, E71700F4, C3C5895A, `82E9A5BC`, B1CB36C6, `0F57D4BE`, `2E61DE89`, `6DDE6EE2`, `743C28E9`, FD01323E, `531599D5`, B06E...
code := [`97021CC7`, `54040CA1`, `06F2443C`, C9539308, C4271880, AADF5BAE, E5E255B3, E71700F4, C3C5895A, `82E9A5BC`, B1CB36C6, `0F57D4BE`, `2E61DE89`, `6DDE6EE2`, `743C28E9`, FD01323E, `531599D5`, B06E...

Decryption reverses the process with the same key

>    cipher2 := unblocker(code,4);
v1 := keyVect:
decipher1 := []:
for i from 1 to messlength do
  oneStep := vectorToBinByte(v1):
  oneByte := byteXOR(oneStep, hexToBinByte(cipher2[i])):
  oneChar := binByteToChar(oneByte):
  v1 := shiftWord(shiftMat,v1):
  decipher1 := [op(decipher1), oneChar]:
od:
decipher1:
cat(op(decipher1));

cipher2 := [`97`, `02`, `1C`, C7, `54`, `04`, `0C`, A1, `06`, F2, `44`, `3C`, C9, `53`, `93`, `08`, C4, `27`, `18`, `80`, AA, DF, `5B`, AE, E5, E2, `55`, B3, E7, `17`, `00`, F4, C3, C5, `89`, `5A`, `82...
cipher2 := [`97`, `02`, `1C`, C7, `54`, `04`, `0C`, A1, `06`, F2, `44`, `3C`, C9, `53`, `93`, `08`, C4, `27`, `18`, `80`, AA, DF, `5B`, AE, E5, E2, `55`, B3, E7, `17`, `00`, F4, C3, C5, `89`, `5A`, `82...
cipher2 := [`97`, `02`, `1C`, C7, `54`, `04`, `0C`, A1, `06`, F2, `44`, `3C`, C9, `53`, `93`, `08`, C4, `27`, `18`, `80`, AA, DF, `5B`, AE, E5, E2, `55`, B3, E7, `17`, `00`, F4, C3, C5, `89`, `5A`, `82...
cipher2 := [`97`, `02`, `1C`, C7, `54`, `04`, `0C`, A1, `06`, F2, `44`, `3C`, C9, `53`, `93`, `08`, C4, `27`, `18`, `80`, AA, DF, `5B`, AE, E5, E2, `55`, B3, E7, `17`, `00`, F4, C3, C5, `89`, `5A`, `82...


Exercise:

2) Use LFSR to encrypt a message and post it to the bulletin board.  You need to post the encrypted message, the polynomial, and the initial key.  Respond to someone else's message.

>   

>