 ### PYTHON CRYPTOGRAPHY PLAYFAIR CIPHER ALGORITHM

#### DESCRIPTION:

Playfair cipher is the first and best-known digraph substitution cipher, which uses the technique of symmetry encryption, Playfair cipher is the best-known multi-letter encryption cipher.

##### EXPLANATION:
Generating the Key square:
1) It is based on a 5 x 5 matrix of letters constructed using a keyword. Here all 25 letters in the matrix are unique.
2) Firstly fill the matrix with the letters of the keyword (dropping any duplicate letters).
3) Now fill the remaining empty locations with the rest of the letters of the alphabets in order (A...Z).
4) As a 5x5 matrix can hold only 25 elements, and 2 alphabets can be paired. (It is a convention to put both "I" and "J" in the same space.)

Encrypting the Plain Text:
Before encrypting the text, you need to divide the Playfair cipher plaintext into digraphs - pairs of two letters. In the case of plaintext with an odd number of letters, add the letter 'Z' to the last letter. If there are any double letters in the plain text, replace the second occurrence of the letter with 'Z', e.g., "butter" -> "butzer"

Rules for Playfair Cipher Encryption:
1. Case I: Both the letters in the digraph are in the same row, Consider the letters right of each alphabet. Thus, if one of the digraph letters is the rightmost alphabet in the grid, consider the leftmost alphabet in the same row.
2. Case II: Both the letters in the digraph is in the same column, Consider the letters below each alphabet. Thus, if one of the digraph letters is the grid's bottommost letter, consider the topmost alphabet in the same column.
3. Case III: Neither Case I or II is true, Form a rectangle with the two letters in the digraph and consider the rectangle's horizontal opposite corners.

Decrypting the Cipher Text:
The decryption of the Playfair cipher follows the same process in reverse. The receiver has the same key and key table and can decrypt the message using the key.

Rules for Playfair Cipher Decryption:
1. Case I: Both the letters in the digraph are in the same row, Consider the letters left of each alphabet. Thus, if one of the digraph letters is the leftmost letter in the grid, consider the rightmost alphabet in the same row.
2. Case II: Both the letters in the digraph is in the same column, Consider the letters above each alphabet. Thus, if one of the digraph letters is the topmost letter in the grid, consider the bottommost alphabet in the same column.
3. Case III: Neither Case I or II is true, Form a rectangle with the two letters in the digraph and consider the rectangle's horizontal opposite corners. ##### CODE:

`import itertoolsimport stringfrom typing import Generator, Iterabledef chunker(seq: Iterable[str], size: int) -> Generator[tuple[str, ...], None, None]:    it = iter(seq)    while True:        chunk = tuple(itertools.islice(it, size))        if not chunk:            return        yield chunkdef prepare_input(dirty):    """    Prepare the plaintext by up-casing it    and separating repeated letters with X's    """    dirty = "".join([c.upper() for c in dirty if c in string.ascii_letters])    clean = ""    if len(dirty) < 2:        return dirty    for i in range(len(dirty) - 1):        clean += dirty[i]        if dirty[i] == dirty[i + 1]:            clean += "X"    clean += dirty[-1]    if len(clean) & 1:        clean += "X"    return cleandef generate_table(key: str) -> list[str]:    # I and J are used interchangeably to allow    # us to use a 5x5 table (25 letters)    alphabet = "ABCDEFGHIKLMNOPQRSTUVWXYZ"    table = []    # copy key chars into the table if they are in `alphabet` ignoring duplicates    for char in key.upper():        if char not in table and char in alphabet:            table.append(char)    # fill the rest of the table in with the remaining alphabet chars    for char in alphabet:        if char not in table:            table.append(char)    return tabledef encrypt(plaintext, key):    table = generate_table(key)    plaintext = prepare_input(plaintext)    ciphertext = ""    for char1, char2 in chunker(plaintext, 2):        row1, col1 = divmod(table.index(char1), 5)        row2, col2 = divmod(table.index(char2), 5)        if row1 == row2:            ciphertext += table[row1 * 5 + (col1 + 1) % 5]            ciphertext += table[row2 * 5 + (col2 + 1) % 5]        elif col1 == col2:            ciphertext += table[((row1 + 1) % 5) * 5 + col1]            ciphertext += table[((row2 + 1) % 5) * 5 + col2]        else:  # rectangle            ciphertext += table[row1 * 5 + col2]            ciphertext += table[row2 * 5 + col1]    return ciphertextdef decrypt(ciphertext, key):    table = generate_table(key)    plaintext = ""    for char1, char2 in chunker(ciphertext, 2):        row1, col1 = divmod(table.index(char1), 5)        row2, col2 = divmod(table.index(char2), 5)        if row1 == row2:            plaintext += table[row1 * 5 + (col1 - 1) % 5]            plaintext += table[row2 * 5 + (col2 - 1) % 5]        elif col1 == col2:            plaintext += table[((row1 - 1) % 5) * 5 + col1]            plaintext += table[((row2 - 1) % 5) * 5 + col2]        else:  # rectangle            plaintext += table[row1 * 5 + col2]            plaintext += table[row2 * 5 + col1]    return plaintextprint("1. Encrypt the Text")print("2. Decrypt the Text")mode = int(input("Enter the mode : "))if(mode==1):  msg = input("Enter your message: ")  key = input("Enter the key: ")  result = encrypt(msg, key)  print(result)elif(mode==2):  msg = input("Enter your message: ")  key = input("Enter the key: ")  result = decrypt(msg, key)  print(result)else:  print("Invalid Input")`

##### OUTPUT:  