CCS Practical Lab Manual
CCS Practical Lab Manual
CCS Practical Lab Manual
One Time Pad algorithm is the improvement of the Vernam Cipher, proposed by An Army
Signal Corp officer, Joseph Mauborgne. It is the only available algorithm that is
unbreakable(completely secure). It is a method of encrypting alphabetic plain text. It is one
of the Substitution techniques which converts plain text into ciphertext. In this mechanism,
we assign a number to each character of the Plain-Text.
The key should be randomly generated as long as the size of the message.
The key is to be used to encrypt and decrypt a single message, and then it is discarded.
So encrypting every new message requires a new key of the same length as the new
message in one-time pad.
The ciphertext generated by the One-Time pad is random, so it does not have any statistical
relation with the plain text.
The assignment is as follows:
A B C D E F G H I J
0 1 2 3 4 5 6 7 8 9
K L M N O P Q R S T
10 11 12 13 14 15 16 17 18 19
U V W X Y Z
20 21 22 23 24 25
The relation between the key and plain text: In this algorithm, the length of the key should
be equal to that of plain text.
Examples:
Input: Message = HELLO, Key = MONEY Output: Cipher – TSYPM, Message – HELLO
Explanation: Part 1: Plain text to Ciphertext Plain text — H E L L O ? 7 4 11 11 14
Key — M O N E Y ? 12 14 13 4 24 Plain text + key ? 19 18 24 15 38 ? 19 18 24 15 12 (= 38
– 26) Cipher Text ? T S Y P M Part 2: Ciphertext to Message Cipher Text — T S Y P
M ? 19 18 24 15 12 Key — M O N E Y? 12 14 13 4 24 Cipher text – key ? 7 4 11 11 -
12 ? 7 4 11 11 14 Message ? H E L L O Input: Message = SAVE, Key = LIFE Output: Cipher
– DIAI Message – SAVE
If any way cryptanalyst finds these two keys using which two plaintext are produced but if
the key was produced randomly, then the cryptanalyst cannot find which key is more likely
than the other. In fact, for any plaintext as the size of ciphertext, a key exists that produces
that plaintext.
So if a cryptanalyst tries the brute force attack(try using all possible keys), he would end up
with many legitimate plaintexts, with no way of knowing which plaintext is legitimate.
Therefore, the code is unbreakable.
The security of the one-time pad entirely depends on the randomness of the key. If the
characters of the key are truly random, then the characters of the ciphertext will be truly
random. Thus, there are no patterns or regularities that a cryptanalyst can use to attack the
ciphertext.
Advantages
One-Time Pad is the only algorithm that is truly unbreakable and can be used for low-
bandwidth channels requiring very high security(ex. for military uses).
Disadvantages
There is the practical problem of making large quantities of random keys. Any heavily used
system might require millions of random characters on a regular basis.
For every message to be sent, a key of equal length is needed by both sender and receiver.
Thus, a mammoth key distribution problem exists.
Below is the implementation of the Vernam Cipher:
Program
Python program Implementing One Time Pad Algorithm
for i in range(len(key)):
x = cipher[i] + ord('A')
cipherText += chr(x)
# Method 2
# Returning plain text
def stringDecryption(s, key):
plain = []
for i in range(len(key)):
plain.append(ord(s[i]) - ord('A') - (ord(key[i]) - ord('A')))
for i in range(len(key)):
x = plain[i] + ord('A')
plainText += chr(x)
# Returning plainText
return plainText
plainText = "Hello"
# Declaring key
key = "MONEY"
GCD of two numbers is the largest number that divides both of them. A simple way to find
GCD is to factorize both numbers and multiply common factors. GCD
// Driver Program
int main()
{
int x, y;
int a = 35, b = 15;
int g = gcdExtended(a, b, &x, &y);
printf("gcd(%d, %d) = %d", a, b, g);
return 0;
}
Output:
gcd(35, 15) = 5
Time Complexity: O(Log min(a, b))
Given a prime number n, the task is to find its primitive root under modulo n. The primitive
root of a prime number n is an integer r between[1, n-1] such that the values of r^x(mod n)
where x is in the range[0, n-2] are different. Return -1 if n is a non-prime number.
Examples:
Input : 7
Output : Smallest primitive root = 3
Explanation: n = 7
3^0(mod 7) = 1
3^1(mod 7) = 3
3^2(mod 7) = 2
3^3(mod 7) = 6
3^4(mod 7) = 4
3^5(mod 7) = 5
Input : 761
Output : Smallest primitive root = 6
A simple solution is to try all numbers from 2 to n-1. For every number r, compute values of
r^x(mod n) where x is in the range[0, n-2]. If all these values are different, then return r, else
continue for the next value of r. If all values of r are tried, return -1.
# Corner cases
if (n <= 1):
return False
if (n <= 3):
return True
return True
x = x % p # Update x if it is more
# than or equal to p
return res
s.add(i)
n = n // i
# Check if r^((phi)/primefactors)
# mod n is 1 or not
if (power(r, phi // it, n) == 1):
flag = True
break
# Driver Code
n = 761
print("Smallest primitive root of",
n, "is", findPrimitive(n))
A client (for example browser) sends its public key to the server and requests some data.
The server encrypts the data using the client’s public key and sends the encrypted data.
The client receives this data and decrypts it.
Since this is asymmetric, nobody else except the browser can decrypt the data even if a third
party has the public key of the browser.
The idea! The idea of RSA is based on the fact that it is difficult to factorize a large integer.
The public key consists of two numbers where one number is a multiplication of two large
prime numbers. And private key is also derived from the same two prime numbers. So if
somebody can factorize the large number, the private key is compromised. Therefore
encryption strength totally lies on the key size and if we double or triple the key size, the
strength of encryption increases exponentially. RSA keys can be typically 1024 or 2048 bits
long, but experts believe that 1024-bit keys could be broken in the near future. But till now it
seems to be an infeasible task.
Let us learn the mechanism behind the RSA algorithm : >> Generating Public Key:
p=3
q=7
n = p*q
e=2
phi = (p-1)*(q-1)
k=2
d = (1 + (k*phi))/e
# Message to be encrypted
msg = 12.0
# Encryption c = (msg ^ e) % n
c = pow(msg, e)
c = math.fmod(c, n)
print("Encrypted data = ", c)
# Decryption m = (c ^ d) % n
m = pow(c, d)
m = math.fmod(m, n)
print("Original Message Sent = ", m)
import random
import math
public_key = None
private_key = None
n = None
ret = next(it)
prime.remove(ret)
return ret
def setkeys():
global public_key, private_key, n
prime1 = pickrandomprime() # First prime number
prime2 = pickrandomprime() # Second prime number
n = prime1 * prime2
fi = (prime1 - 1) * (prime2 - 1)
e=2
while True:
if math.gcd(e, fi) == 1:
break
e += 1
d=2
while True:
if (d * e) % fi == 1:
break
d += 1
private_key = d
def decoder(encoded):
s = ''
# Calling the decrypting function decoding function
for num in encoded:
s += chr(decrypt(num))
return s
if __name__ == '__main__':
primefiller()
setkeys()
message = "Test Message"
# Uncomment below for manual input
# message = input("Enter the message\n")
# Calling the encoding function
coded = encoder(message)
print("Initial message:")
print(message)
print("\n\nThe encoded message(encrypted by public key)\n")
print(''.join(str(p) for p in coded))
print("\n\nThe decoded message(decrypted by public key)\n")
print(''.join(str(p) for p in decoder(coded)))
Output
Initial message:
Test Message
5. DES Algorithm
Data encryption standard (DES) has been found vulnerable to very powerful attacks
andhttps://media.geeksforgeeks.org/wp-content/uploads/20200306122641/DES-11.png
therefore, the popularity of DES has been found slightly on the decline. DES is a block cipher
and encrypts data in blocks of size of 64 bits each, which means 64 bits of plain text go as
the input to DES, which produces 64 bits of ciphertext. The same algorithm and key are used
for encryption and decryption, with minor differences. The key length is 56 bits.
We have mentioned that DES uses a 56-bit key. Actually, The initial key consists of 64 bits.
However, before the DES process even starts, every 8th bit of the key is discarded to
produce a 56-bit key. That is bit positions 8, 16, 24, 32, 40, 48, 56, and 64 are discarded.
Thus, the discarding of every 8th bit of the key produces a 56-bit key from the original 64-bit
key.
DES is based on the two fundamental attributes of cryptography: substitution (also called
confusion) and transposition (also called diffusion). DES consists of 16 steps, each of which is
called a round. Each round performs the steps of substitution and transposition. Let us now
discuss the broad-level steps in DES.
In the first step, the 64-bit plain text block is handed over to an initial Permutation (IP)
function.
The initial permutation is performed on plain text.
Next, the initial permutation (IP) produces two halves of the permuted block; saying Left
Plain Text (LPT) and Right Plain Text (RPT).
Now each LPT and RPT go through 16 rounds of the encryption process.
In the end, LPT and RPT are rejoined and a Final Permutation (FP) is performed on the
combined block
The result of this process produces 64-bit ciphertext.
Initial Permutation (IP):
As we have noted, the initial permutation (IP) happens only once and it happens before the
first round. It suggests how the transposition in IP should proceed, as shown in the figure.
For example, it says that the IP replaces the first bit of the original plain text block with the
58th bit of the original plain text, the second bit with the 50th bit of the original plain text
block, and so on.
This is nothing but jugglery of bit positions of the original plain text block. the same rule
applies to all the other bit positions shown in the figure.
As we have noted after IP is done, the resulting 64-bit permuted text block is divided into
two half blocks. Each half-block consists of 32 bits, and each of the 16 rounds, in turn,
consists of the broad-level steps outlined in the figure.
Step-1: Key transformation:
We have noted initial 64-bit key is transformed into a 56-bit key by discarding every 8th bit
of the initial key. Thus, for each a 56-bit key is available. From this 56-bit key, a different 48-
bit Sub Key is generated during each round using a process called key transformation. For
this, the 56-bit key is divided into two halves, each of 28 bits. These halves are circularly
shifted left by one or two positions, depending on the round.
For example: if the round numbers 1, 2, 9, or 16 the shift is done by only one position for
other rounds, the circular shift is done by two positions. The number of key bits shifted per
round is shown in the figure.
After an appropriate shift, 48 of the 56 bits are selected.From the 48 we might obtain 64 or
56 bits based on requirement which helps us to recognize that this model is very versatile
and can handle any range of requirements needed or provided. for selecting 48 of the 56
bits the table is shown in the figure given below. For instance, after the shift, bit number 14
moves to the first position, bit number 17 moves to the second position, and so on. If we
observe the table , we will realize that it contains only 48-bit positions. Bit number 18 is
discarded (we will not find it in the table), like 7 others, to reduce a 56-bit key to a 48-bit
key. Since the key transformation process involves permutation as well as a selection of a
48-bit subset of the original 56-bit key it is called Compression Permutation.
Because of this compression permutation technique, a different subset of key bits is used in
each round. That makes DES not easy to crack.
def hex2bin(s):
mp = {'0': "0000",
'1': "0001",
'2': "0010",
'3': "0011",
'4': "0100",
'5': "0101",
'6': "0110",
'7': "0111",
'8': "1000",
'9': "1001",
'A': "1010",
'B': "1011",
'C': "1100",
'D': "1101",
'E': "1110",
'F': "1111"}
bin = ""
for i in range(len(s)):
bin = bin + mp[s[i]]
return bin
def bin2hex(s):
mp = {"0000": '0',
"0001": '1',
"0010": '2',
"0011": '3',
"0100": '4',
"0101": '5',
"0110": '6',
"0111": '7',
"1000": '8',
"1001": '9',
"1010": 'A',
"1011": 'B',
"1100": 'C',
"1101": 'D',
"1110": 'E',
"1111": 'F'}
hex = ""
for i in range(0, len(s), 4):
ch = ""
ch = ch + s[i]
ch = ch + s[i + 1]
ch = ch + s[i + 2]
ch = ch + s[i + 3]
hex = hex + mp[ch]
return hex
def bin2dec(binary):
binary1 = binary
decimal, i, n = 0, 0, 0
while(binary != 0):
dec = binary % 10
decimal = decimal + dec * pow(2, i)
binary = binary//10
i += 1
return decimal
def dec2bin(num):
res = bin(num).replace("0b", "")
if(len(res) % 4 != 0):
div = len(res) / 4
div = int(div)
counter = (4 * (div + 1)) - len(res)
for i in range(0, counter):
res = '0' + res
return res
# S-box Table
sbox = [[[14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7],
[0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8],
[4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0],
[15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13]],
# Initial Permutation
pt = permute(pt, initial_perm, 64)
print("After initial permutation", bin2hex(pt))
# Splitting
left = pt[0:32]
right = pt[32:64]
for i in range(0, 16):
# Expansion D-box: Expanding the 32 bits data into 48 bits
right_expanded = permute(right, exp_d, 48)
# S-boxex: substituting the value from s-box table by calculating row and column
sbox_str = ""
for j in range(0, 8):
row = bin2dec(int(xor_x[j * 6] + xor_x[j * 6 + 5]))
col = bin2dec(
int(xor_x[j * 6 + 1] + xor_x[j * 6 + 2] + xor_x[j * 6 + 3] + xor_x[j * 6 + 4]))
val = sbox[j][row][col]
sbox_str = sbox_str + dec2bin(val)
# Swapper
if(i != 15):
left, right = right, left
print("Round ", i + 1, " ", bin2hex(left),
" ", bin2hex(right), " ", rk[i])
# Combination
combine = left + right
pt = "123456ABCD132536"
key = "AABB09182736CCDD"
# Key generation
# --hex to binary
key = hex2bin(key)
# Splitting
left = key[0:28] # rkb for RoundKeys in binary
right = key[28:56] # rk for RoundKeys in hexadecimal
rkb = []
rk = []
for i in range(0, 16):
# Shifting the bits by nth shifts by checking from shift table
left = shift_left(left, shift_table[i])
right = shift_left(right, shift_table[i])
rkb.append(round_key)
rk.append(bin2hex(round_key))
print("Encryption")
cipher_text = bin2hex(encrypt(pt, rkb, rk))
print("Cipher Text : ", cipher_text)
print("Decryption")
rkb_rev = rkb[::-1]
rk_rev = rk[::-1]
text = bin2hex(encrypt(cipher_text, rkb_rev, rk_rev))
print("Plain Text : ", text)
Decryption
After initial permutation: 19BA9212CF26B472
After splitting: L0=19BA9212 R0=CF26B472
Encryption:
Decryption
After initial permutation: 19BA9212CF26B472
After splitting: L0=19BA9212 R0=CF26B472
6. Digital Signature
Note: You can refer this link for better understanding of cryptographic terms.
Digital Signatures are often calculated using elliptical curve cryptography, especially in IoT
devices, but we will be using RSA for demonstration purposes. First, we will take the input
message and create a hash of it using SHA-256 because of its speed and security, and we will
then encrypt that hash with the private key from Asymmetric key pair. On the other side, the
receiver will decrypt it using the public key and compare the hash to ensure they are indeed
the same.
Digital Signature Flow
Let “A” and “B” be the fictional actors in the cryptography system for better understanding.
“A” is the sender and calculates the hash of the message and attaches signature which he
wants to send using his private key.
The other side “B” hashes the message and then decrypts the signature with A’s public key
and compares the two hashes
If “B” finds the hashes matching then the message has not been altered or compromised.
Implementing Digital Signatures
Let us implement the digital signature using algorithms SHA and RSA and also verify if the
hash matches with a public key.
Approach:
package java_cryptography;
// Imports
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.util.Scanner;
import javax.xml.bind.DatatypeConverter;
// Signing Algorithm
private static final String
SIGNING_ALGORITHM
= "SHA256withRSA";
private static final String RSA = "RSA";
private static Scanner sc;
// Driver Code
public static void main(String args[])
throws Exception
{
String input
= "GEEKSFORGEEKS IS A"
+ " COMPUTER SCIENCE PORTAL";
KeyPair keyPair
= Generate_RSA_KeyPair();
// Function Call
byte[] signature
= Create_Digital_Signature(
input.getBytes(),
keyPair.getPrivate());
System.out.println(
"Signature Value:\n "
+ DatatypeConverter
.printHexBinary(signature));
System.out.println(
"Verification: "
+ Verify_Digital_Signature(
input.getBytes(),
signature, keyPair.getPublic()));
}
}
Output:
Where ‘a’ is the co-efficient of x and ‘b’ is the constant of the equation
The curve is non-singular; that is, its graph has no cusps or self-intersections (when the
characteristic of the Coefficient field is equal to 2 or 3).
In general, an elliptic curve looks like as shown below. Elliptic curves can intersect almost 3
points when a straight line is drawn intersecting the curve. As we can see, the elliptic curve is
symmetric about the x-axis. This property plays a key role in the algorithm.
Diffie-Hellman algorithm:
The Diffie-Hellman algorithm is being used to establish a shared secret that can be used for
secret communications while exchanging data over a public network using the elliptic curve
to generate points and get the secret key using the parameters.
For the sake of simplicity and practical implementation of the algorithm, we will consider
only 4 variables, one prime P and G (a primitive root of P) and two private values a and b.
P and G are both publicly available numbers. Users (say Alice and Bob) pick private values a
and b and they generate a key and exchange it publicly. The opposite person receives the
key and that generates a secret key, after which they have the same secret key to encrypt.
Step-by-Step explanation is as follows:
Alice Bob
Public Keys available = P, G Public Keys available = P, G
Private Key Selected = a Private Key Selected = b
Key generated =
x = G^a mod P
Key generated =
y = G^b mod P
k_a = k_b
def prime_checker(p):
# Checks If the number entered is a Prime Number or not
if p < 1:
return -1
elif p > 1:
if p == 2:
return 1
for i in range(2, p):
if p % i == 0:
return -1
return 1
l = []
while 1:
P = int(input("Enter P : "))
if prime_checker(P) == -1:
print("Number Is Not Prime, Please Enter Again!")
continue
break
while 1:
G = int(input(f"Enter The Primitive Root Of {P} : "))
if primitive_check(G, P, l) == -1:
print(f"Number Is Not A Primitive Root Of {P}, Please Try Again!")
continue
break
# Private Keys
x1, x2 = int(input("Enter The Private Key Of User 1 : ")), int(
input("Enter The Private Key Of User 2 : "))
while 1:
if x1 >= P or x2 >= P:
print(f"Private Key Of Both The Users Should Be Less Than {P}!")
continue
break
if k1 == k2:
print("Keys Have Been Exchanged Successfully")
else:
print("Keys Have Not Been Exchanged Successfully")
Output:
The value of P : 23
The value of G : 9
We are given two arrays num[0..k-1] and rem[0..k-1]. In num[0..k-1], every pair is coprime
(gcd for every pair is 1). We need to find minimum positive number x such that:
x % num[0] = rem[0],
x % num[1] = rem[1],
.......................
x % num[k-1] = rem[k-1]
Basically, we are given k numbers which are pairwise coprime, and given remainders of
these numbers when an unknown number x is divided by them. We need to find the
minimum possible value of x that produces given remainders.
Examples :
Input: num[] = {5, 7}, rem[] = {1, 3}
Output: 31
Explanation:
31 is the smallest number such that:
(1) When we divide it by 5, we get remainder 1.
(2) When we divide it by 7, we get remainder 3.
The first part is clear that there exists an x. The second part basically states that all solutions
(including the minimum one) produce the same remainder when divided by-product of
num[0], num[1], .. num[k-1]. In the above example, the product is 3*4*5 = 60. And 11 is one
solution, other solutions are 71, 131, .. etc. All these solutions produce the same remainder
when divided by 60, i.e., they are of form 11 + m*60 where m >= 0.
A Naive Approach to find x is to start with 1 and one by one increment it and check if
dividing it with given elements in num[] produces corresponding remainders in rem[]. Once
we find such an x, we return it.
Below is the implementation of Naive Approach.
# Check if remainder of
# x % num[j] is rem[j]
# or not (for all j from
# 0 to k-1)
j = 0;
while(j < k):
if (x % num[j] != rem[j]):
break;
j += 1;
# If all remainders
# matched, we found x
if (j == k):
return x;
# Driver Code
num = [3, 4, 5];
rem = [2, 3, 1];
k = len(num);
print("x is", findMinX(num, rem, k));
x is 11
Time Complexity : O(M), M is the product of all elements of num[] array.