computer science python

profileCurse Yi
pa7.pdf

CptS 111 — PA #7 Due Friday, Apr. 20, 2018

ENCRYPTION USING THE LOGISTIC MAP FUNCTION

Header information. Your program must include the following information in the header.

• Name

• Programming Assignment #7

• Date

• Name of program

• Brief description and/or purpose of the program; include sources

Background. In Lab #11 you wrote (or will write) a function called clear2cipher() that takes

two arguments, a string and an integer offset, and applies the same offset to the ASCII value for each

character in the string. The offset is applied to each character using the function cipher chr()

which produces a printable ASCII character regardless of the offset by wrapping the set of printable

characters around on itself. Thus, an offset of 1 applied to a tilde (’˜’, which is the last ASCII

printable graphics character and has an ASCII value of 126) results in a space (’ ’, which is the first

printable graphics character and has an ASCII value of 32). In Lab #11 you also wrote (or will write)

a function called cipher2clear() that takes two arguments, a string and an offset, and undoes

what clear2cipher() has done, i.e., it deciphers the ciphered string.

The ultimate goal of encryption is to convert clear text to cipher text in such a way that it can be read

(or decrypted) only by the intended recipients. It is assumed that it doesn’t matter if the cipher text

falls into the “wrong” hands, i.e., if “eavesdroppers” see it, because they don’t know the “key” or

“password” without which the cipher text can’t be decrypted. Only the intended recipients have this

information.

With encryption such as the Caesar cipher, i.e., using a constant offset for each character, cracking the

code is just too easy. However, suppose we use a different offset for each character and moreover that

this offset is “random” but can still be deciphered using a password. This is where the logistic map

function comes in! Recall that for the logistic map function, f(a, x) = ax(1−x), the value of f(a, x)

depends on the value of a, the amplitude, and x, the normalized population of a given generation.

When a is larger than 3.57, the behavior of successive values of x, i.e., different generations, is

chaotic as we saw in Lab #11.

In Lab #11, you wrote a function called compare() that you used to compare the generations for

two different initial populations xa and xb. For convenience, I’m relisting the results for an amplitude

a = 3.99:

1 Enter the amplitude a: 3.99

2 Enter the number of generations n: 200

1

3 Enter the two initial populations: 0.30001, 0.30000

4 Gen. # xa xb xa - xb

5 1 0.8379159596010001 0.8379 1.595960100009286e-05

6 2 0.5418930889452718 0.5419361241000001 -4.303515472836583e-05

7 3 0.9904974267035079 0.9904830323669228 1.4394336585121792e-05

8 4 0.03755497484497509 0.03761131589099112 -5.634104601603268e-05

9 5 0.14421694885037983 0.14442485218367743 -0.00020790333329759325

10 6 0.4924394978535204 0.49302959387904743 -0.0005900960255270093

11

12 [output deleted]

13

14 196 0.8047371028191858 0.9971523890640764 -0.19241528624489068

15 197 0.6269698396798223 0.011329613171045869 0.6156402265087765

16 198 0.933175852648604 0.04469299961539693 0.8884828530332071

17 199 0.248811135921798 0.17035518624909252 0.07845594967270547

18 200 0.7457475767067793 0.5639238441008467 0.18182373260593254

Note again that although the inital values of x are not very different, eventually the generations of x

seem to have no relationship to each other; their behavior is chaotic. How can we exploit this chaotic

behavior? The logistic map function always returns values between 0 and 1 (when 0 < a < 4). Recall

that we want offsets between 0 and 95 for encryption (see Lab #11) so suppose we do the following:

multiply xa and xb by 96 and then take their integer parts. Let’s see what we get.

1 Enter the amplitude a: 3.99

2 Enter the number of generations n: 200

3 Enter the two initial populations: 0.30001, 0.30000

4 Gen. # xa xb

5 1 80 80

6 2 52 52

7 3 95 95

8 4 3 3

9 5 13 13

10 6 47 47

11

12 [output deleted]

13

14 196 77 95

15 197 60 1

16 198 89 4

17 199 23 16

18 200 71 54

What did we get? Notice that in the second and third columns the values range from 1 to 95 (in

fact, when more generations are used, they actually range between 0 and 95) and, thus, they work

2

perfectly as offsets for encryption! Initially the two sequences of offsets are identical but after many

generations, they differ radically. We can think of the starting values 0.30001 or 0.30000 together

with the amplitude a = 3.99 as numerical passwords, and in order to decrypt cipher text with offsets

generated by the logistic map function, these numbers have to be precisely right. However, it’s difficult

to remember numbers with lots of digits, so rather than using an initial value of x and an amplitude

a, we’ll use a more refined and fun way of generating a and x using the function gen amp seed

compliments of Prof. John Schneider.

The function shown below, called gen amp seed(), can be obtained from the class website. It

takes one string argument, a password, which is used to generate an amplitude a and an initial (seed)

value, i.e., the first x in the logistic map function. In the example shown above with the two different

initial values of xa and xb, the first several generations of sequences are very similar. However, this

is not the case when using gen amp seed(). Even a change of one bit in the password will result

in a completely different sequence of offsets immediately. What is important in using this function is

to note what it returns!

1 #######################################################################

2 # Function to calculate an "amplitude" and a "seed" appropriate for

3 # starting the encryption/decryption process. These values are based

4 # on the password given as the argument (which is assumed to be a

5 # string). This function uses the SHA1 hash function from the hashlib

6 # module. See http://en.wikipedia.org/wiki/SHA-1 for details

7 # concerning this function. The hash function creates a unique

8 # 160-bit ’digest’ for a given string. This digest is split into two

9 # parts. One part is used to set the amplitude and the other is used

10 # to set the seed value.

11 import hashlib

12

13 def gen_amp_seed(password):

14 h = hashlib.sha1() # Create a SHA1 hash object.

15 h.update(password.encode()) # Update object with password.

16 # Create a 160-bit hexadecimal digest which is returned as a

17 # 40-character string.

18 d = h.hexdigest()

19 # Split the digest into two parts, each of 20 hexadecimal

20 # characters/digits.

21 d1 = d[ : 20]

22 d2 = d[20 : ]

23 # max_possible is the maximum possible value that 20 hexadecimal

24 # digits can have.

25 max_possible = 0xFFFFFFFFFFFFFFFFFFFF

26 # Set the amp and seed values. amp is 3.99 plus a number that is

27 # between 0.0 and 0.01 while seed is between 0.0 and 1.0. In the

3

28 # unlikely event that d2 equals max_possible, then seed will be

29 # 1.0 which will cause the log_map() function to return zero.

30 # Although simple to account for this, we will not do so here.

31 amp = 3.99 + eval(’0x’ + d1) / max_possible / 100

32 seed = eval(’0x’ + d2) / max_possible

33 return amp, seed

The following code demonstrates the behavior of this function:

1 >>> # Import gen_amp_seed().

2 >>> from gen_amp_seed() import *

3 >>> # Call gen_amp_seed() with an argument of ’undercover’.

4 >>> password = input(’Enter password: ’)

5 Enter password: undercover

6 >>> gen_amp_seed(password)

7 (3.99901475987551, 0.5975199126206723)

8 >>> # Call gen_amp_seed() with an argument of ’agent99’.

9 >>> a, x = gen_amp_seed(’agent99’)

10 >>> print(a, x)

11 (3.9914234187336097, 0.20519277633205749)

12 >>> # Show how amplitude and seed can be used with logistic map

13 >>> # function to obtain a suitable offset for encryption.

14 >>> offset = int(96 * x)

15 >>> print(offset)

16 19

17 >>> x = log_map(a, x)

18 >>> offset = int(96 * x)

19 >>> print(offset)

20 62

The user is prompted for a password in line 4 and responds with undercover in line 5. The function

gen amp seed() is then called with the password argument. In line 6 gen amp seed() returns

the amplitude and seed values. A different password agent99 is used in line 9 and the results are

shown in lines 10 and 11. As expected, the amplitude and seed values are different. Lines 14 through

20 show how the seed can be used to obtain an offset and how the logistic map can be used to generate

the next offset using the seed.

This assignment consists of four parts; parts A and C are for encryption while parts B and D are

for decryption. All four parts use the gen amp seed() function. The functions cipher chr(),

decipher chr(), and log map() from Lab #11 are also used. In part A you must define a func-

tion called line2cipher() that is also used in part C, while in part B you must define a function

called line2clear() that is also used in part D. Create a file called cipher.py and place or copy

the log map(), gen amp seed(), cipher chr(), and decipher chr() functions into this

file. You’ll need to import this module for use with the functions you’ll create in this assignment.

4

To reduce duplication, save line2cipher() and line2clear() (parts A and B) in this file as

well. You’ll then only need to use one import statement in parts C and D.

Part A

In an IDLE Editor window write a function called line2cipher() that takes three arguments.

The first two arguments are floats, an amplitude and a seed value. The third argument is a line

of clear text (i.e., a string). This function will encrypt an entire line of clear text. It’s similar to

the clear2cipher() function you wrote in Lab #11. The difference is that rather than using a

constant offset, you’ll use a new offset determined by the log map() function for each character.

Here’s what you need to do:

• Start by initializing a string accumulator as an empty string.

• Use a for-loop to encrypt each character in the line using one iteration of the loop for each

character.

• In the loop body, add each encrypted character to the accumulator as it’s encrypted, calling the

function cipher chr() to encrypt the character, and then update the offset by calling the

log map() function with the amplitude and current value of the offset as arguments. Note

that the offset is a value between 0 and 1, but you need to pass a value between 0 and 95 to the

cipher chr() function. This value should be the integer part of the current offset multiplied

by 96 (see paragraph 5 of the background section above).

• Return the amplitude, the final value of the offset returned by the log map() function, and

the ciphered line of text.

The following demonstrates the behavior of this function. In this example we convert the clear text

string ’This is a test.’ to cipher text using the password ’I’ve got a secret!’. Note

that you need to import the cipher module to run this code.

1 >>> # Import cipher module.

2 >>> from cipher import *

3 >>> # Convert clear text to cipher text

4 >>> password = input(’Enter password: ’)

5 Enter password: I’ve got a secret!

6 >>> amp, seed = gen_amp_seed(password)

7 >>> amp, seed

8 (3.992407363218947, 0.1910140581380398)

9 >>> line = input(’Enter line to cipher: ’)

10 Enter line to cipher: This is a test.

11 >>> line2cipher(amp, seed, line)

12 (3.992407363218947, 0.5416116867372278, ’fDd(‘ˆ;|nMtgz1˜’)

5

The last line is a tuple, the third element of which is the cipher text ’fDd(‘ˆ;|nMtgz1˜’. Notice

that the space characters appearing in the fifth, eighth, and tenth positions were encrypted to ‘, |,

and M, i.e., 3 different characters. Also, the amplitude returned by line2cipher(), i.e., the first

value in the tuple, is the same as the original amplitude passed to the function. However, the second

element in the tuple, representing the final value returned by the logistic map function, is different

than the value passed as the second argument to the function.

If we only want to obtain the cipher text and not the other two return values, we can choose the third

element in the tuple directly. For example,

1 >>> print(line2cipher(amp, seed, line)[2])

2 fDd(‘ˆ;|nMtgz1˜

Add your line2cipher() function to the cipher module.

Part B

In an IDLE Editor window (or in your file cipher.py if it’s easier for you), write a function called

line2clear() to decrypt the line you encrypted using line2cipher() in part A. line2clear()

also takes three arguments. The first and second are the amplitude and offset obtained using the same

password used in part A with the gen amp seed() function. The third argument is the line of

cipher text.

line2clear() is essentially a copy of line2cipher except that it calls decipher chr()

instead of cipher chr(). It returns the amplitude, the final value of the offset calculated by the

logistic map function, and the deciphered line of text.

The following demonstrates the behavior of this function.

1 >>> # Import cipher module.

2 >>> from cipher import *

3 >>> # Convert clear text to cipher text.

4 >>> amp, seed = gen_amp_seed(‘‘I’ve got a secret!’’)

5 >>> amp, seed

6 (3.992407363218947, 0.1910140581380398)

7 >>> line2clear(amp, seed, ’fDd(‘ˆ;|nMtgz1˜’)

8 (3.992407363218947, 0.5416116867372278, ’This is a test.’)

9 >>> # Try again, but alter the last bit of the last character of

10 >>> # password (ord(’!’) = 33 and ord(’’’’) = 34).

11 >>> amp, seed = gen_amp_seed(‘‘I’ve got a secret"’’)

12 >>> amp, seed

13 (3.992011996345999, 0.9376985383034211)

6

14 >>> line2clear(amp, seed, ’fDd(‘ˆ;|nMtgz1˜’)

15 (3.992011996345999, 0.13280615797484494, ‘‘k. 9’b,HnJf7z1{‘‘)

As can be seen in the first part of this example, the line2clear() function successfully decrypts

the cipher text. Perhaps more interestingly, lines 12 through 15 show what happens when the password

is changed just a little. Even this small change in the password leads to radically different results.

Hence, without the password, it would be very difficult to crack this result.

If necessary, add line2clear() to your cipher module.

Part C

In part A, you wrote a program to encrypt one line of clear text. This was kind of cool, but encrypting

one line of text that you actually have to type is rather restrictive and not particularly practical. Sup-

pose you want to encrypt an entire file of text so that no one without the password can read it. In part

C, you must write a program to accomplish this. This program will consist of three parts:

• An import statement: Import your cipher module.

• A function main(): This function prompts the user for a password and calls the gen amp seed()

function to generate the amplitude and seed. It prompts for the name of the clear text file (the

input file) and also for the name of the cipher text file (the output file). It opens these files

and then encrypts the clear text line-by-line using a for-loop. At each iteration of the loop,

it passes one line of text to the line2cipher() function together with the amplitude and

offset. Recall that the line2cipher() function returns a tuple of three elements: the am-

plitude, the last offset generated by the log map() function, and the line of cipher text. The

former two of these are the arguments in the next iteration so they must have the save names

(e.g., amp and offset). Finally, it prints the cipher text (the last tuple element) to the output

file.

• A call to main().

Reread the second item above as many times as necessary to really understand what you need to do.

It isn’t hard, and the entire function body consists of 9 or fewer statements. Instructions for opening

the files are given in the next paragraph.

Recall that you open a file using the open() command. The first argument is the file name and

the second argument indicates whether you are opening the file for reading or writing (both these

arguments are strings). To open the input file for reading, the second argument is the character ’r’;

to open the output file for writing, the second argument is the character ’w’. open() returns a file

object. When a file object is used as the iterable in a for-loop header, the loop variable takes on the

value of a line in the file for each iteration of the loop, i.e., it is a string. This string variable includes

the newline character at the end of each line. We remove the newline character using the rstrip()

method (or strip()). Printing to an output file is straightforward. In the following example, we

7

open an input file called data.txt and an output file called result.txt. Then in a for-loop,

we iterate through each line of the input file, removing the whitespace at the end of each line and

printing the result to the output file.

1 file_in = open(’data.txt’, ’r’)

2 file_out = open(’result.txt’, ’w’)

3 for line in file_in:

4 print(line.rstrip(), file=file_out)

The following demonstrates the proper behavior of this program. The cipher text isn’t part of the

output, but is provided so you can check your program.

1 Enter password: Oh woe is me.

2 Enter clear text file name: clear.txt

3 Enter cipher text file name: cipher.txt

4

5 lQQ]eWPyyiV_Cm1iVji,XMcml}cTsi*N{v@jmM

6 >=}m6k’’<suz;ˆ@{$Mn% Gf#fOof#$8xv($a=lozs5 b|.2 boo?k ow@e/

7 G%ImOc|Qw’fNtii0>gw$e:ZFh|tka85d#(@u!i(Edg$SSjl3c˜9ck8‘R

Download the clear text file from the class website, and use it as your input file. Save your program

as encrypt.py. Don’t forget to include docstrings.

Part D

Make a copy of the program you wrote in part C and call it decrypt.py. Make appropriate edits

to it so that it can be used to decrypt the cipher text file you created in part C. It requires only one

change to make it work properly, but it’s good form to change the text, names, and of course the

docstrings appropriately. Here’s how it should work:

1 Enter password: Oh woe is me.

2 Enter cipher text file name: cipher.txt

3 Enter clear text file name: myclear.txt

Compare the contents of the clear.txt and myclear.txt files. They should be the same. If not,

debug your program! I’ve included a secret message on our class website. I’ve tweeted the password

@wsuprofmom.

Submission information. Use Blackboard to submit a zipped file called

<first initial+lastname> pa7.zip. Submit PA #7 as a zip file up to 3 times before the

deadline. Don’t forget that style counts, and don’t forget docstrings.

8