C代写:CS162 Crypto

代写一个比较杂的作业,从开始的Vigénere cipher到Hash Table, 最后是AVL Tree.

Introduction

In this assignment, you will explore the applications of hash tables to encode plaintext using simple substitution ciphers in cryptography. Work individually. Follow the guidelines in the syllabus for any discussions with others. You may work in pairs on extra credit contests, but then the extra credit will be divided among the two of you. Extra credit entries must be bug-free to be eligible and turned in on time (no late days).

Files

After downloading the assignment tarball from the course website, extract the files by running:

tar -xvf lab3-handout.tgz

from a terminal window. Some of the files worth looking at are listed below.
Files you won’t necessarily modify:

Makefile
lib/*.h
lib/*.c
lib/words

Files you should modify (and the files denoted by * will be submitted for grading):

*vc-ht.c
*encrypt.c
*decrypt.c
*vigenere_test.c

Additionally, you should create a file called: written.pdf which contains the answers to the written parts of the assignment. For your convenience, we’ve provided a LATEX template (written.tex).

Submission

To submit your programming assignment, first generate a handin archive lab3-handin.tgz with the command

make clean && make package

then submit your lab3-handin.tgz file to your Subversion repository (svn) for the class. Once you’ve completed all problems, you should also submit your written.pdf to Gradescope.

Note that your code will not be graded until after the due date. It is your responsibility to test your code on the CSIL machines and proofread your writeup thoroughly before submitting. Code that does not compile will not receive any points.

Word Substitution Cipher

Due to your excellent work in parsing hashtags in lab 2, you are reached out by an agent from the Never Segfault Academy, also known as the NSA.1 To ensure secure communication among their newly set up computer network and prevent any data from being intercepted and copied, they intend to implement an encryption protocol. Your job is to implement a simple word substitution cipher using hash tables in C for their secure transmission network.

Vigénere cipher

After doing some research on different types of substitution cipher, you find that the Vigénere cipher may be a good start. So in this part of the assignment, you will implement a modified version of the Vigénere cipher.

First, a little cryptographic background. Let’s say you’re sitting in class bored, and you want to pass a note to your friend sitting way on the other side of the classroom. To get the note there, you’ll have to relay your note through a lot of your classmates. However, perhaps the message contains some secret information that you don’t want any classmate but your friend to know (name of your crush, your Netflix password, etc). The purpose of a cryptographic cipher is to encrypt the message in a way so that only you and your friend can figure out, or decrypt, the original message.

In order to accomplish this, you and your friend need some shared, secret piece of information, called a key. A cryptographic cipher is a function that takes a key k and a message m, and outputs a ciphertext c which is the encoding of m. There is also a matching decryption function that takes the key k and a ciphertext c, and outputs the original message m.

One of the very first ciphers ever used was the Caesar cipher. We will describe a slightly modified version of the Caesar cipher based on words (if you want to read about the original version, you can find it on Wikipedia). The key is a number, and encryption offsets the words in the message by the key value. The easiest way to describe this is by example. Let’s say all words in the message must come from some “language” L, for example

L = ["NSA", "CIA", "IRA", "PPAP", "PW", "DJT", "HRC", "IS", "WIFI"]

and we want to pass the message “WIFI PW IS CIA”. Notice that the “language” L in this example is not sorted in any order, but you can safely assume that the dictionary lib/words you will use later is sorted alphabetically. Furthermore, words in this example L are all capitalized, however the language we will use in later sections are all in lowercase. Using key 1, each word becomes the one at the next index in the language (with the last looping back) e.g. “NSA” becomes “CIA”, “CIA” becomes “IRA”, … , “IS” becomes “WIFI”, and “WIFI” becomes “NSA”. On key 2, each word becomes the one that is two indices away e.g. “NSA” becomes “IRA”, . . . , “HRC” becomes “WIFI”, “IS” becomes “NSA”, and “WIFI” becomes “CIA”. Applying this to each word, on key 4 the message “WIFI PW IS CIA” becomes “PPAP WIFI IRA DJT”. To decrypt, we would replace each word with the one 4 words before it.

The Vigénere cipher you are implementing is an improvement on this. Instead of using just a single number for a key, we use an entire phrase. Each word in the phrase is associated with its index in the language, and we shift the words in our message by that index as in the Caesar cipher.

Again, an example is the best description. Let’s use key “HRC DJT PPAP DJT” to encipher “WIFI PW
IS CIA”. We start by aligning the words of the message with key:

HRCD DJT PPAP DJT
WIFI PWD ISDD CIA

“HRC” is the 6th word of the language (0 index!), so we shift “WIFI” by 6 (like the Caesar cipher) to “DJT”. “DJT” is the 5th word, so we shift “PW” by 5 to “NSA”, and so on. The entire ciphertext becomes

DJT NSA CIA HRC

To decrypt, we align the key words with the ciphertext words, and take each words back by each index. Finally, we don’t want to require that the key and message have the same length. To get around this, we just repeat the key as many times as necessary to cover the message. For example, encrypting with key “DJT IS HRC” would produce

DJTD IS HRC DJT IS HRC
WIFI PW ISD CIA         →   PW IRA PW HRC

Task 4.1

Using the above language, encrypt “NSA IS CIA IS IRA” using the key “PW PPAP WIFI”.
Ok! For your next tasks, we work with a generic language, which we enumerate as w1, w2, …, wn and the l-word key as wk1 … wkl.

Task 4.2

If the message is wm1 wm2 … wmd and the ciphertext is wc1 wc2 … wcd, write a mathematical expression for encrypting i.e. computing the wci from the wmi .

Task 4.3

Write a mathematical expression for decrypting i.e. computing the wmi from the wci.

Hash Tables

As you discovered in the previous tasks, the best way to implement the Vigénere cipher is to associate each word in the language with a index. We could create an array of words, sorted in alphabetical order, and therefore find each word by its index. However, during the encipher/decipher process, we also need to be able to compute the index of a word efficiently.

Therefore, we will be using both arrays and hash tables to implement the Vigénere cipher protocol. In this part of the assignment, you are going to implement a wrapper around the generic hash tables implementation that is provided to you. So you do not have to implement the hash table from scratch. The wrapper will serve as an interface specifically for inserting and looking up the words used in Vigénere cipher.

The functions you will implement in vc-ht.c should respect the header interface in lib/vc-ht.h.
Specifically, you will need to complete the functions, vc_ht_new, vc_ht_insert, vc_ht_lookup, vc_ht_free, and other helper functions for the underlying hash table. Note that this hash table stores each word as its element struct vc_ht_elem_base which has two fields. The field idx is the index of the word in the dictionary lib/words. The field word is of type word_t, which is just a typedef of char *, and is the string value of the word stored in the hash table element.

Task 4.4

In vc-ht.c, implement the helper functions, each of which should be less than two lines (except for hash), as required by vc_ht_new. Notice that they will be passed into (the generic) ht_new as function pointers, and their argument and return types do not have the “vc_“ prefix, which means you will probably need to think carefully about casting before writing your code.

For instance, elem_key takes an input e of type ht_elem, but in order to read the value stored in e, you might need to cast to the proper type first.

More specifically, the helper functions are:

  • elem_key: Read off the key stored in the element.
  • key_equal: Key comparison function for the hash table.
  • hash: Hash function that maps each key to its appropriate location in the hash table. A good hash function should be efficient and should minimize the number of collisions.
  • elem_free: Tell the hash table how each element should be freed.

Task 4.5

In vc-ht.c, implement the function in less than two lines: vc_ht_elem vc_ht_insert(ht H, vc_ht_elem e) that adds the element e to the hash table H, and returns the old element with key of e that is replaced, if it exists, and returns NULL otherwise.

Task 4.6

In vc-ht.c, implement the function in less than two lines: vc_ht_elem vc_ht_lookup(ht H, vc_ht_key k) that returns NULL if no element with key k exists and the element associated with the key otherwise.

Task 4.7

In vc-ht.c, implement the function in less than two lines: void vc_ht_free(ht H) that frees the hash table and all of its data.

Encryption

Now we can finally put everything together and start implement the encryption protocol.

Task 5.1

In encrypt.c, implement the function:

1
2
3
void decrypt_msg(word_t *A, ht HT, int wordcount,
word_t *key_sentence, int key_len,
word_t *cipher_text, word_t *plain_text, int txt_len)

Task 5.2

And conversely, in decrypt.c, implement the function:

1
2
3
void decrypt_msg(word_t *A, ht HT, int wordcount,
word_t *key_sentence, int key_len,
word_t *cipher_text, word_t *plain_text, int txt_len)

For your convenience, we have implemented some utility functions to test your implementation.
You might want to check out the functions in lib/english.h and vigenere-test.c.
In lib/english.h:

  • word_cmp: Comparison function for type word_t.
  • print_sentence: Print an array of words to stdout.

In vigenere-test.c:

  • init_word_array: Allocate and initialize an array of words from lib/words.
  • free_word_array: Free the memory used by the array of words.
  • init_word_ht: Allocate and initialize a hash table from the word array.
  • free_word_ht: Free the memory used by the hash table of words.

Feel free to use/modify the functions implemented in vigenere-test.c, which is also where all your tests should go. We have provided you with a sample test in the main function, however it is strongly recommended that you add more of your own tests and run:

make vigenere

Runtime Complexity

Consider the following function that sorts the integers in an array, assuming swap and is_sorted were implemented, where swap(A, i, j) swaps the array element A[i] and A[j], and is_sorted(A, i, j) checks if A[i,j) is sorted.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void sort(int[] A, int n)
// pre_condition: 0 <= n && n <= length(A);
// post_condition: is_sorted(A, 0, n);
{

for (int i = 0; i < n; i++)
// loop_invariant: 0 <= i && i <= n;
// loop_invariant: is_sorted(A, _____, _____);
{
int s = 0;
for (int j = 0; j < n-i-1; j++)
// loop_invariant: 0 <= j && j <= n−i−1;
// loop_invariant: s > 0 || (s == 0 && is_sorted(A, 0, j));
{
if (A[j] > A[j+1]) {
swap(A, j, j+1); // function that swaps A[j] and A[j+1]
s = s + 1;
}
}
if (s == 0) return;
}
}

Task 6.1

Complete the missing loop invariant (at line 7) after the first i iterations.

Task 6.2

The asymptotic complexity of this sort depends on the number of comparisons made between pairs of array elements. Let T(n) be the worst-case number of such comparisons made when sort(A, n) is called. Give a closed form expression for T(n).

Task 6.3

Using Big-O notation, what is the asymptotic complexity of T(n)? This is the worst-case runtime complexity of sort.

Task 6.4

Using your answer from the previous part, show that T(n) ∈ O(f(n)) using the formal definition of Big-O. That is, find a c > 0 and n0 ≥ 0 such that for every n ≥ n0, T(n) ≤ cf(n). Show your work.

Task 6.5

Using Big-O notation, what is the best case asymptotic complexity of this sort as a function of n.

AVL Trees

Task 7.1

Draw the AVL trees that result after successively inserting the following keys into an initially empty tree, in the order shown:

27, 16, 8, 9, 6, 12, 7

Show the tree after each insertion and subsequent re-balancing (if any) is completed: the tree after the first element, 27, is inserted into an empty tree, then the tree after 16 is inserted into the first tree, and so on for a total of seven trees. Make it clear what order the trees are in.

Hashing

For the following two tasks, we use the hash function:

1
2
3
int hash(int x) {
return x + (x % 3) * 4;
}

As seen from lecture, we say that if the hash value of a key k is hash(k), then the index that we insert this key to is hash(k)%m, where m is the size of the hash table.

Task 8.1

Draw the resulting hash table of size 7 after inserting the following values, using linear probing to resolve collisions:

84, 44, 66, 16, 105, 76

Task 8.2

Draw the resulting hash table of size 7 after inserting the following values, using quadratic probing to resolve collisions:

84, 44, 66, 16, 105, 76