0% found this document useful (0 votes)
15 views21 pages

Experiment 1

Uploaded by

mohammed.ansari
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
15 views21 pages

Experiment 1

Uploaded by

mohammed.ansari
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 21

Experiment - 1

Name: Ansari Mohammed Shanouf Valijan


Class: B.E. Computer Engineering, Semester - VII
UID: 2021300004
Batch: VII

Aim:
To implement different substitution techniques.

Theory:
Substitution ciphers are a class of encryption techniques in which each letter in the plaintext
is replaced by a corresponding letter in the ciphertext according to a fixed system. The key
to the cipher is the method of substitution, which can vary in complexity. Simple
substitution ciphers may shift the alphabet by a certain number of positions, while more
complex methods might involve using multiple alphabets or more elaborate systems. The
main advantage of substitution ciphers is their simplicity and ease of implementation, but
they are also vulnerable to frequency analysis, a method of breaking the cipher by studying
the frequency of letters in the ciphertext. Following are the substitution techniques taken
into consideration for the experiment-

Caesar Cipher
The Caesar cipher is one of the earliest and simplest substitution ciphers, attributed to
Julius Caesar, who reportedly used it to communicate with his generals. In this cipher, each
letter in the plaintext is shifted a fixed number of places down or up the alphabet. For
example, with a shift of 3, 'A' becomes 'D', 'B' becomes 'E', and so on. The Caesar cipher is
easy to understand and implement but offers very little security, as there are only 25
possible shifts, making it highly susceptible to brute-force attacks.

Monoalphabetic Cipher
The monoalphabetic cipher is a more generalized form of the Caesar cipher. In this type of
substitution cipher, each letter of the plaintext is mapped to a fixed, but arbitrarily chosen,
letter of the ciphertext alphabet. Unlike the Caesar cipher, the mapping is not restricted to a
fixed shift but can be any permutation of the alphabet. While this increases the number of
possible keys, making brute-force attacks impractical, the cipher is still vulnerable to
frequency analysis since each letter in the plaintext corresponds to only one letter in the
ciphertext.

Playfair Cipher
The Playfair cipher is a digraphic substitution cipher, meaning it encrypts pairs of letters
rather than single letters. Developed by Charles Wheatstone in 1854, but popularized by
Lord Playfair, this cipher uses a 5x5 grid filled with a keyword and the remaining letters of
the alphabet (I and J are usually combined). To encrypt, pairs of letters from the plaintext
are located in the grid and substituted according to specific rules. The Playfair cipher offers
more security than monoalphabetic ciphers by disguising letter frequencies, but it is still
vulnerable to specialized cryptanalytic techniques.

Hill Cipher
The Hill cipher is a polygraphic substitution cipher invented by Lester S. Hill in 1929. It uses
linear algebra to transform blocks of plaintext letters into ciphertext. The encryption process
involves multiplying a matrix of numbers (derived from the plaintext letters) by an invertible
key matrix. The resulting matrix is then converted back into letters to produce the
ciphertext. The Hill cipher is notable for its use of matrix multiplication and provides
security by mixing the plaintext letters in a complex way, but it requires a key matrix that is
invertible modulo 26, which can be a limitation.

Polyalphabetic Cipher
The polyalphabetic cipher is an extension of the substitution cipher that uses multiple
substitution alphabets to encrypt the plaintext. The most famous example is the Vigenère
cipher, where a keyword determines which alphabet is used to encrypt each letter. This
method significantly complicates frequency analysis, as the same letter in the plaintext can
be encrypted in different ways depending on its position in the text. Polyalphabetic ciphers
provide better security than monoalphabetic ciphers, but they can still be broken with
enough ciphertext, especially if the keyword is short or repetitive.

Implementation:
Following are the codes for encryption and decryption for all the techniques taken into
consideration-
[1] Caesar Cipher
def encrypt(plaintext, key):
encrypted_text = ''
for char in plaintext:
encrypted_char = chr((ord(char) - 97 + key) % 26 + 97)
encrypted_text += encrypted_char
return encrypted_text.upper()

def decrypt(ciphertext, key):


decrypted_text = ''
for char in ciphertext:
decrypted_char = chr((ord(char) - 65 - key) % 26 + 65)
decrypted_text += decrypted_char
return decrypted_text.lower()

def brute_force_attack(ciphertext):
for i in range(26):
print(i, ' ----> ', decrypt(ciphertext, i))

# with open('plaintext.txt', 'r') as file:


# plaintext = file.read()

# with open('ciphertext_caesar.txt', 'w') as file:


# file.write(encrypt(plaintext, 17))

[2] Monoalphabetic Cipher


from random import randint

def encrypt(plaintext, key):


encrypted_text = ''
for char in plaintext:
encrypted_char = key[char]
encrypted_text += encrypted_char
return encrypted_text.upper()

def decrypt(ciphertext, key):


decrypted_text = ''
for char in ciphertext.lower():
decrypted_char = key[char]
decrypted_text += decrypted_char
return decrypted_text

def genkey():
alphabet_mapping = {}
alphabet_list = [chr(i) for i in range(97, 123)]
for i in range(26):
randindex = randint(0, len(alphabet_list) - 1)
alphabet_mapping[chr(i + 97)] = alphabet_list.pop(randindex)
return alphabet_mapping

def inversekey(origkey):
inversekey = {}
for key, value in origkey.items():
inversekey[value] = key
return inversekey

# with open('plaintext.txt', 'r') as file:


# plaintext = file.read()

# with open('ciphertext_monoalphabetic.txt', 'w') as file:


# file.write(encrypt(plaintext, genkey()))

[3] Playfair Cipher


def genkey(keyword):
updated_keyword = ''
for char in keyword:
if char == 'j':
char = 'i'
updated_keyword += char
updated_keyword += 'abcdefghiklmnopqrstuvwxyz'

keyset = {
'original_mapping': {},
'reverse_mapping': {}
}

row_counter = 0
column_counter = 0
for char in updated_keyword:
if char not in keyset['original_mapping']:
keyset['original_mapping'][char] = (row_counter, column_counter)
column_counter += 1
if column_counter == 5:
column_counter = 0
row_counter += 1

for key, value in keyset['original_mapping'].items():


keyset['reverse_mapping'][value] = key

return keyset

def getDiagraphs(plaintext):
updated_plaintext = ''
for char in plaintext:
if char == 'j':
char = 'i'
updated_plaintext += char

diagraphs = []

index = 0
strlength = len(updated_plaintext)
while(True):
if index == strlength:
break
if index == strlength - 1:
diagraphs.append(updated_plaintext[index] + 'x')
break
if updated_plaintext[index] != updated_plaintext[index+1]:
diagraphs.append(updated_plaintext[index] +
updated_plaintext[index+1])
index += 2
else:
diagraphs.append(updated_plaintext[index] + 'x')
index += 1

return diagraphs

def encrypt(plaintext, key):


diagraphs = getDiagraphs(plaintext)
encrypted_text = ''

for diagraph in diagraphs:


index1 = key['original_mapping'][diagraph[0]]
index2 = key['original_mapping'][diagraph[1]]

if index1[0] == index2[0]:
encrypted_text += key['reverse_mapping'][(index1[0], (index1[1] + 1) %
5)]
encrypted_text += key['reverse_mapping'][(index2[0], (index2[1] + 1) %
5)]
elif index1[1] == index2[1]:
encrypted_text += key['reverse_mapping'][((index1[0] + 1) % 5,
index1[1])]
encrypted_text += key['reverse_mapping'][((index2[0] + 1) % 5,
index2[1])]
else:
encrypted_text += key['reverse_mapping'][(index1[0], index2[1])]
encrypted_text += key['reverse_mapping'][(index2[0], index1[1])]

return encrypted_text.upper()

def decrypt(ciphertext, key):


updated_ciphertext = ciphertext.lower()
decrypted_text = ''

for i in range(0, len(ciphertext), 2):


index1 = key['original_mapping'][updated_ciphertext[i]]
index2 = key['original_mapping'][updated_ciphertext[i+1]]

if index1[0] == index2[0]:
decrypted_text += key['reverse_mapping'][(index1[0], (index1[1] - 1) %
5)]
decrypted_text += key['reverse_mapping'][(index2[0], (index2[1] - 1) %
5)]
elif index1[1] == index2[1]:
decrypted_text += key['reverse_mapping'][((index1[0] - 1) % 5,
index1[1])]
decrypted_text += key['reverse_mapping'][((index2[0] - 1) % 5,
index2[1])]
else:
decrypted_text += key['reverse_mapping'][(index1[0], index2[1])]
decrypted_text += key['reverse_mapping'][(index2[0], index1[1])]

return decrypted_text

# with open('plaintext.txt', 'r') as file:


# plaintext = file.read()

# with open('ciphertext_playfair.txt', 'w') as file:


# file.write(encrypt(plaintext, genkey('xysdneskvovsgkfji')))

[4] Hill Cipher


from random import randint

def determinant(matrix):
n = len(matrix)
if n == 1:
return matrix[0][0]
if n == 2:
return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]

det = 0
for c in range(n):
minor = [[matrix[r][i] for i in range(n) if i != c] for r in range(1, n)]
det += ((-1) ** c) * matrix[0][c] * determinant(minor)
return det

def cofactor(matrix):
n = len(matrix)
cofactor_matrix = [[0] * n for _ in range(n)]
for r in range(n):
for c in range(n):
minor = [[matrix[i][j] for j in range(n) if j != c] for i in range(n)
if i != r]
cofactor_matrix[r][c] = ((-1) ** (r + c)) * determinant(minor)
return cofactor_matrix

def transpose(matrix):
return list(map(list, zip(*matrix)))

def adjoint(matrix):
cofactor_matrix = cofactor(matrix)
return transpose(cofactor_matrix)

def inversemodulo(number, modval):


inverseval = 0
for i in range(modval):
if (number*i) % modval == 1:
inverseval = i
break
return inverseval

def getkeyinv(key):
detval = determinant(key)
moduloinval = inversemodulo(detval, 26)
adjmat = adjoint(key)
for i in range(len(key)):
for j in range(len(key)):
adjmat[i][j] = ((adjmat[i][j] % 26) * moduloinval) % 26
return adjmat

def encrypt(plaintext, key):


groups = [plaintext[i : i + len(key)] for i in range(0, len(plaintext),
len(key))]
encrpyted_text = ''

for group in groups:


charvals = [0 for _ in range(len(key))]
for i in range(len(group)):
charvals[i] = ord(group[i]) - 97
for i in range(len(key)):
keyvals = [key[j][i] for j in range(len(key))]
encodedval = sum(x*y for x, y in zip(charvals, keyvals)) % 26
encrpyted_text += chr(encodedval + 97)

return encrpyted_text.upper()

def decrypt(ciphertext, key):


updated_ciphertext = ciphertext.lower()
groups = [updated_ciphertext[i : i + len(key)] for i in range(0,
len(updated_ciphertext), len(key))]
decrpyted_text = ''
for group in groups:
charvals = [0 for _ in range(len(key))]
for i in range(len(group)):
charvals[i] = ord(group[i]) - 97
for i in range(len(key)):
keyvals = [key[j][i] for j in range(len(key))]
decodedval = sum(x*y for x, y in zip(charvals, keyvals)) % 26
decrpyted_text += chr(decodedval + 97)

return decrpyted_text

# with open('plaintext.txt', 'r') as file:


# plaintext = file.read()

# with open('ciphertext_hill.txt', 'w') as file:


# file.write(encrypt(plaintext, [[6, 24, 1], [13, 16, 10], [20, 17, 15]]))

[5] Polyalphabetic Cipher


def encrypt(plaintext, keyword):
encrypted_text = ''

while(len(keyword) < len(plaintext)):


keyword += keyword

for i in range(len(plaintext)):
encrypted_text += chr((ord(plaintext[i]) + ord(keyword[i]) - 2*97) % 26 +
97)

return encrypted_text.upper()

def decrypt(ciphertext, keyword):


ciphertext = ciphertext.lower()
decrypted_text = ''

while(len(keyword) < len(ciphertext)):


keyword += keyword

for i in range(len(ciphertext)):
decrypted_text += chr((ord(ciphertext[i]) - ord(keyword[i])) % 26 + 97)

return decrypted_text

# with open('plaintext.txt', 'r') as file:


# plaintext = file.read()

# with open('ciphertext_polyalphabetic.txt', 'w') as file:


# file.write(encrypt(plaintext, 'wxisnehididfnnskank'))
Testing:
Following are the images depicting encryption and decryption tests that were carried out to
confirm the proper functioning of the codes-

plaintext used: ‘thisisaverysecretmessage’

[1] Caesar Cipher

[2] Monoalphabetic Cipher

[3] Playfair Cipher

Key matrix as generated from the keyword-

[4] Hill Cipher


[5] Polyalphabetic Cipher

Attack:
[1] Caesar Cipher
Following is an image depicting the brute-force attack on Caesar cipher (Encrypted message
was taken from group member)-

Decrypted text found when key was set as 12 (encrypted text at key 0).
Decrypted text: Lab Eleven Under Construction
(Decryptions of other encrypted texts received from group member are mentioned in the
plain-cipher-text-pairs-received.txt)

[2] Monoalphabetic Cipher


def brute_force_attack(ciphertext):

for key in keys:


print('\n\n-----------------------------------------------')
print('key ----> ', key)
print('Decrpted Message -----> ', decrypt(ciphertext, inversekey(key)))

brute_force_attack('HIFVAGYEVZFYZOBYZFYK')

Following shows a list of trials with different keys, the decrypted message being ‘north west
area cleared’
[3] Playfair Cipher
keywords = [chr(i) for i in range(97, 123)] + ['a'+chr(i) for i in range(97, 123)]

def brute_force_attack(ciphertext):
for keyword in keywords:
print('\n\n------------------------------------------')
print('keyword -----> ', keyword)
print('decrypted text -----> ', decrypt(ciphertext, genkey(keyword)))
brute_force_attack('PCDUHOFCQIFYKUNW')
The decrypted message was found at keyword ‘ac’, the message being ‘meeting at five pm’.
A redundant x was observed at the end of the decrypted message.

[4] Hill Cipher


keys = []
for a, b, c, d in itertools.product(range(26), repeat=4):
keys.append([[a, b], [c, d]])

def brute_force_attack(ciphertext):
for key in keys:
print('\n\n---------------------------------------')
print('key ----> ', key)
print('decrypted message ----> ', decrypt(ciphertext, getkeyinv(key)))

brute_force_attack('UXLHUQFFPARTUKSHGJ')
Decrypted message was found to be ‘reply if help needed’ at key [[2, 3], [3, 6]]

[5] Polyalphabetic Cipher


keywords = [chr(i) for i in range(97, 123)] + ['a'+chr(i) for i in range(97, 123)]

def brute_force_attack(ciphertext):
for keyword in keywords:
print('\n\n------------------------------------------')
print('keyword -----> ', keyword)
print('decrypted text -----> ', decrypt(ciphertext, keyword))

brute_force_attack('CRNIIUMBOXRLDHNWIWY')

The decrypted message was found to be ‘confirm your identity’ when keyword was ‘ad’.
Below is the output that was used to find the keyword and the message.
Plots:
For the purpose of understanding the nature of substitution cipher techniques, a text
containing about 17000 characters was processed to obtain the plaintext, the code of which
is as follows-
import re

with open('text_to_encrypt.txt', 'r', encoding="utf-8") as file:


text = file.read()

text = text.lower()
processed_text = re.sub(r'[^a-z]', '', text)

with open('plaintext.txt', 'w') as file:


file.write(processed_text)

Further, the generated plaintext was converted to cipher text using various techniques
mentioned above. Each of the text files were analysed for the frequency of all the
characters (a to z) in them.
Following are the bar graphs showing the frequency of each letter in the text files-
Following is a plot depicting the relative frequency of letters in case of cipher texts
generated using the techniques mentioned above (ranked in the descending order)-
Conclusion:
By performing this experiment, I was able to understand the mechanism of various
substitution cipher techniques. I was able to successfully code the logic of encryption and
decryption for the techniques mentioned and was able to verify the correctness using
various examples from the group member. I was able to use the implementations to break
the Caesar cipher using brute force and was able to decrypt messages generated using
other techniques. I was also able to use the implemented encryption algorithms to perform
frequency analysis on the data, following which, I was able to infer that monoalphabetic
cipher is very susceptible to such an analysis. Hill cipher shows an almost flat graph, making
it difficult to break using the above analysis.

You might also like