CS305 Lab: Cryptography Experiments
CS305 Lab: Cryptography Experiments
AND ENGINEERING
CS305
LAB FILE
2
Experiment 1
Theory: The Caesar Cipher is one of the earliest and simplest encryption techniques,
named after Julius Caesar, who reportedly used it to protect his military communications. It is
a type of substitution cipher, where each letter in the plaintext is shifted by a fixed number
of positions down the alphabet.
Code:
#include <bits/stdc++.h>
using namespace std;
class caeserCipher{
private:
int key;
char shiftChar(char c, int shift){
if(c >= 'a' && c<='z'){
return 'a' + (c - 'a' + shift + 26) % 26;
}
else if(c >= 'A' && c <= 'Z'){
return 'A' + (c - 'A' + shift + 26) % 26;
}
return c;
}
public:
caeserCipher(int n){
key = n;
}
return cypherText;
}
3
}
return plainText;
}
};
int main(){
caeserCipher cipher(20);
string plainText = "Aayushbansal23cs010";
string encryptedText = [Link](plainText);
string decryptedText = [Link](encryptedText);
cout << "Encrypted Text: " << encryptedText << endl;
cout << "Decrypted Text: " << decryptedText << endl;
return 0;
}
Output:
4
Experiment 2
Theory: A monoalphabetic cipher is a type of substitution cipher where each letter of the
plaintext is replaced with another unique letter of the alphabet. Unlike the Caesar cipher,
which shifts characters by a fixed number of positions, the monoalphabetic cipher allows any
random permutation of the 26 letters to act as the key, thus providing 26!26!26! possible
keys. This makes it more secure than Caesar cipher against brute force attacks. However, it is
still vulnerable to frequency analysis because in English and most languages, certain letters
and letter combinations occur more frequently than others. By analyzing the frequency of
ciphertext letters and comparing them with the known frequency distribution of the language,
an attacker can often break the cipher. Decryption in a monoalphabetic cipher requires the
exact key mapping used during encryption, which substitutes each ciphertext character back
to its corresponding plaintext character.
Code:
#include <bits/stdc++.h>
using namespace std;
class monoAlphabeticCypher{
public:
string key = "qwertyuiopasdfghjklzxcvbnm";
string key2 = "QWERTYUIOPASDFGHJKLZXCVBNM";
string key3 = "9876123450";
unordered_map<char, char> encMap;
unordered_map<char, char> decMap;
monoAlphabeticCypher(){
mapCreation();
}
void mapCreation(){
int i=0;
for(char c: key){
encMap['a' + i] = c;
decMap[c] = 'a' + i;
i++;
}
5
i=0;
for(char c: key2){
encMap['A' + i] = c;
decMap[c] = 'A' + i;
i++;
}
i=0;
for(char c: key3){
encMap['0' + i] = c;
decMap[c] = '0' + i;
i++;
}
}
string encrypt(string plainText){
string cypherText = "";
for(char c: plainText){
if(isalnum(c))
cypherText += encMap[c];
else
cypherText += c;
}
return cypherText;
}
int main(){
monoAlphabeticCypher ac;
string encryptedText = [Link]("AayushBansal 23/cs/010");
string decryptedText = [Link](encryptedText);
cout << "Encrypted Text: " << encryptedText << endl;
cout << "Decrypted Text: " << decryptedText << endl;
return 0;
}
6
Output:
Learning Outcome: Through this experiment, students will learn the working principle
of monoalphabetic substitution ciphers and understand how decryption can be performed
using the appropriate key mapping. They will gain hands-on experience in implementing
decryption algorithms and appreciate the differences between Caesar cipher and
monoalphabetic substitution in terms of key space and security. Students will also understand
the weaknesses of monoalphabetic ciphers, particularly their susceptibility to frequency
analysis, and recognize why such classical cryptographic methods are insufficient for secure
communication in modern systems.
7
Experiment 3
Code:
#include <bits/stdc++.h>
using namespace std;
class playFairCypher {
private:
char matrix[5][5];
unordered_map<char, pair<int, int>> charPos;
8
}
}
int idx = 0;
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
matrix[i][j] = adjustedKey[idx];
charPos[adjustedKey[idx]] = {i, j};
idx++;
}
}
}
9
int col1 = charPos[a].second;
int row2 = charPos[b].first;
int col2 = charPos[b].second;
if (row1 == row2) {
result += matrix[row1][(col1 + shift + 5) % 5];
result += matrix[row2][(col2 + shift + 5) % 5];
} else if (col1 == col2) {
result += matrix[(row1 + shift + 5) % 5][col1];
result += matrix[(row2 + shift + 5) % 5][col2];
} else {
result += matrix[row1][col2];
result += matrix[row2][col1];
}
}
return result;
}
public:
void setKey(string key) {
generateMatrix(key);
}
void displayMatrix() {
cout << "Key Matrix:\n";
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
cout << matrix[i][j] << ' ';
}
cout << '\n';
}
}
};
int main() {
playFairCypher cipher;
string key = "AAYUSHBANSAL";
10
[Link](key);
[Link]();
return 0;
}
Output:
Learning Outcome: By performing this experiment, students will understand the concept
of digraph substitution and the construction of the Playfair cipher key matrix. They will learn
how to implement both encryption and decryption algorithms using the substitution rules of
the cipher. This experiment helps students analyze how Playfair cipher improves upon simple
substitution techniques by encrypting letter pairs, thereby providing better resistance against
frequency analysis. Furthermore, students will develop insight into the limitations of classical
ciphers and appreciate the evolution toward stronger and more complex cryptographic
methods in modern systems.
11
Experiment 4
Code:
#include <bits/stdc++.h>
using namespace std;
class VignereCipher{
private:
string key;
return c;
}
12
return str;
}
public:
VignereCipher(string k = "random"){
string lowerKey = "";
for(int i=0; i<[Link](); i++){
lowerKey += tolower(k[i]);
}
key = lowerKey;
}
};
int main(){
VignereCipher v1("Aayushbansal");
string plainText = “Hello Aayush Bansal";
cout << "Plain Text: " << plainText << endl;
string a = [Link](plainText);
cout << "Cipher Text: " << a << endl;
string b = [Link](a);
cout << "Decrypted Text: " << b << endl;
return 0;
}
13
Output:
Learning Outcome: Through this experiment, students will learn how multiple
substitution alphabets are used to enhance the security of encryption schemes. They will
understand the working principle of polyalphabetic substitution, including both encryption
and decryption processes. Students will gain hands-on experience in implementing the
algorithm, constructing and applying a key for repeated use, and observing how this
technique reduces the weaknesses of monoalphabetic ciphers. This experiment will help them
appreciate the historical evolution of cryptography from simple substitution to more secure
and complex systems, laying the foundation for understanding modern encryption algorithms.
14
Experiment 5
Theory: The Hill cipher is a polyalphabetic substitution cipher based on linear algebra.
It was invented by Lester S. Hill in 1929 and operates on blocks of letters rather than
individual characters, making it a block cipher. In this method, each letter is first converted
into a numerical value (A = 0, B = 1, ..., Z = 25). The plaintext is then divided into equal-
sized blocks, and each block is represented as a column vector. A key matrix of the same size
(n × n) is used for encryption. The ciphertext is obtained by multiplying the key matrix with
the plaintext vector and taking the result modulo 26. Mathematically, it is expressed as:
C = K × P (mod 26)
where Cis the ciphertext vector, Kis the key matrix, and Pis the plaintext vector. For
decryption, the inverse of the key matrix (mod 26) is used:
P = K −1 × C (mod 26)
The key matrix must be invertible modulo 26 (i.e., its determinant should be coprime with
26) to ensure successful decryption. The Hill cipher provides high diffusion and security
compared to classical ciphers like Caesar or Playfair, as it mixes multiple letters at once.
However, it can be vulnerable to known plaintext attacks if a sufficient number of plaintext–
ciphertext pairs are known.
Code:
#include <bits/stdc++.h>
using namespace std;
class HillCipher
{
private:
vector<vector<int>> keyMatrix;
int matrixSize;
15
}
if (matrixSize == 3)
{
int det = mat[0][0] * (mat[1][1] * mat[2][2] - mat[1][2] * mat[2][1]) - mat[0][1] *
(mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]) + mat[0][2] * (mat[1][0] * mat[2][1] - mat[1]
[1] * mat[2][0]);
return ((det % 26) + 26) % 26;
}
16
cof[0][1] = -(mat[1][0] * mat[2][2] - mat[1][2] * mat[2][0]);
cof[0][2] = (mat[1][0] * mat[2][1] - mat[1][1] * mat[2][0]);
cof[1][0] = -(mat[0][1] * mat[2][2] - mat[0][2] * mat[2][1]);
cof[1][1] = (mat[0][0] * mat[2][2] - mat[0][2] * mat[2][0]);
cof[1][2] = -(mat[0][0] * mat[2][1] - mat[0][1] * mat[2][0]);
cof[2][0] = (mat[0][1] * mat[1][2] - mat[0][2] * mat[1][1]);
cof[2][1] = -(mat[0][0] * mat[1][2] - mat[0][2] * mat[1][0]);
cof[2][2] = (mat[0][0] * mat[1][1] - mat[0][1] * mat[1][0]);
return cof;
}
return inv;
}
public:
HillCipher(const string &key, int size)
{
matrixSize = size;
keyMatrix = getKeyMatrix(key);
}
17
string encrypt(const string &text)
{
string result = "";
for (size_t i = 0; i < [Link](); i += matrixSize)
{
vector<int> block(matrixSize, 0);
for (int j = 0; j < matrixSize; j++)
{
if (i + j < [Link]() && isalpha(text[i + j]))
block[j] = (toupper(text[i + j]) - 'A') % 26;
}
vector<int> encryptedBlock = multiplyMatrix(keyMatrix, block);
for (int val : encryptedBlock)
result += char(val + 'A');
}
return result;
}
int main()
{
string text, key;
int size;
18
size = int(sqrt([Link]()));
if ((size * size) != [Link]())
{
cout << "Invalid key length.\n";
return 1;
}
return 0;
}
Output:
Learning Outcome: By performing this experiment, students will learn how linear
algebra concepts such as matrices, determinants, and modular arithmetic are applied in
cryptography. They will understand the process of encrypting and decrypting messages using
the Hill cipher and how to construct and validate the key matrix. This experiment helps
students appreciate how mathematical operations can enhance the strength of cryptographic
systems by introducing complexity and diffusion. Additionally, they will gain practical
experience in implementing matrix-based encryption techniques and understanding the
advantages and limitations of the Hill cipher in secure communication.
19
Experiment 6
Theory: Simplified Data Encryption Standard (S-DES) is a reduced version of the DES
algorithm, designed for educational purposes to help understand the working principles of
modern block ciphers. It operates on 8-bit blocks of plaintext using a 10-bit key, and
performs two rounds of encryption or decryption. The key generation process is a crucial
part of S-DES, as it produces two 8-bit subkeys (K1 and K2) from the initial 10-bit key,
which are later used in each round of encryption and decryption. The subkey generation
involves several permutation and shifting operations. First, the 10-bit key undergoes a P10
permutation, rearranging its bits according to a predefined pattern. The result is divided into
two 5-bit halves, each of which is circularly left-shifted by one position to produce the input
for P8 permutation, generating the first subkey (K1). For the second subkey (K2), both
halves are again circularly shifted left by two positions and passed through the same P8
permutation pattern. These operations ensure that each subkey is unique and derived
systematically from the main key. The subkey generation process in S-DES demonstrates
how permutation, shifting, and key scheduling contribute to the overall security and diffusion
of symmetric key algorithms.
Code:
#include <bits/stdc++.h>
using namespace std;
class DESKeyGenerator {
private:
const vector<int> PC1 = {
57,49,41,33,25,17,9,
1,58,50,42,34,26,18,
10,2,59,51,43,35,27,
19,11,3,60,52,44,36,
63,55,47,39,31,23,15,
7,62,54,46,38,30,22,
14,6,61,53,45,37,29,
21,13,5,28,20,12,4
};
20
};
vector<int> key64;
vector<vector<int>> roundKeys;
public:
DESKeyGenerator(const string& keyStr) {
[Link](64);
for (char c : keyStr) {
key64.push_back(c - '0');
}
void generateRoundKeys() {
vector<int> key56 = permute(key64, PC1);
[Link]();
for (int i = 0; i < 16; i++) {
C = leftShift(C, shifts[i]);
D = leftShift(D, shifts[i]);
vector<int> combined(C);
[Link]([Link](), [Link](), [Link]());
21
vector<int> roundKey = permute(combined, PC2);
roundKeys.push_back(roundKey);
}
}
int main() {
string key =
"0001001100110100010101110111100110011011101111001101111111110001";
DESKeyGenerator des(key);
[Link]();
[Link]();
return 0;
}
Output:
22
Learning Outcome: Through this experiment, students will understand the process of
key generation in symmetric key encryption, particularly how subkeys are derived from an
original key using permutations and shifts. They will learn how the two subkeys (K1 and K2)
are produced in the S-DES algorithm and how these keys contribute to the encryption and
decryption rounds. This experiment helps students gain practical knowledge of key
scheduling, which is a fundamental concept in modern cryptographic algorithms. By
implementing S-DES subkey generation, students will appreciate the importance of key
transformation in enhancing security and preventing predictable encryption patterns in block
cipher systems.
23
Experiment 7
Theory: The Diffie–Hellman Key Exchange algorithm, proposed by Whitfield Diffie and
Martin Hellman in 1976, is one of the earliest public-key cryptographic protocols. It allows
two parties to securely establish a shared secret key over an insecure communication
channel without transmitting the key itself. The algorithm is based on the principles of
modular arithmetic and the discrete logarithm problem, which is computationally hard to
reverse. In this method, both users publicly agree on a large prime number pand a primitive
root g(also called a generator). Each user then selects a private key (say aand b), computes
their public key using the formula A = g a mod pand B = g b mod p, and exchanges these
public keys. Finally, both users compute the shared secret key using the received public key
and their private key:
K = (B a )mod p = (A b )mod p
Since both expressions yield the same result, a common secret key is established that can
later be used for symmetric encryption. The security of the Diffie–Hellman algorithm lies in
the difficulty of computing the discrete logarithm, making it infeasible for an attacker to
determine the private key even with knowledge of the public parameters. However, Diffie–
Hellman is vulnerable to man-in-the-middle attacks if authentication is not implemented.
Code:
#include <bits/stdc++.h>
using namespace std;
int main()
{
long long P, G, x, a, y, b, ka, kb;
P = 31;
cout << "The value of P(Prime) is : " << P << endl;
G = 11;
cout << "The value of G(Primitive Root for P) : " << G << endl;
a = 6;
24
cout << "The private key for A: " << a << endl;
x = power(G, a, P);
b = 7;
cout << "The private key for B : " << b << endl;
y = power(G, b, P);
ka = power(y, a, P);
kb = power(x, b, P);
cout << "Secret key for A is : " << ka << endl;
cout << "Secret key for B is : " << kb << endl;
return 0;
}
Output:
Learning Outcome: Through this experiment, students will understand the concept of
public key cryptography and how two parties can securely generate a shared secret key
without prior communication. They will learn the step-by-step working of the Diffie–
Hellman Key Exchange algorithm using modular exponentiation and the role of prime
numbers and primitive roots in ensuring security. By implementing this algorithm, students
will appreciate how mathematical problems like the discrete logarithm form the foundation of
modern cryptographic systems. This experiment also helps them recognize potential
vulnerabilities in unauthenticated key exchange and the importance of combining Diffie–
Hellman with authentication mechanisms in practical secure communication protocols.
25
Experiment 8
Theory:
RSA (Rivest‒Shamir‒Adleman) is a widely used public-key cryptosystem that enables
secure data transmission. It is based on the mathematical difficulty of factoring large prime
numbers. Unlike symmetric key algorithms, RSA uses two separate keys: a public key for
encryption and a private key for decryption. Working Principle:
[Link] Generation: Two large prime numbers (p and q) are selected. The modulus n = p ×
q iscomputed. Euler’ s totient function φ (n) = (p-1)(q-1) is calculated. A public key
exponent e is chosen such that 1 < e < φ (n) and gcd(e, φ (n)) = 1. The private key d is
computed as the modular multiplicative inverse of e modulo φ (n).
[Link]: The receiver uses the private key d to retrieve the original message:
m = cd mod n
Since decryption can only be performed with the private key, RSA ensures both
confidentiality and authentication. It is widely used in secure communications, digital
signatures, and SSL/TLS protocols.
Code:
#include <bits/stdc++.h>
using namespace std;
26
int main(){
cout<<"=== RSA Key Generation and Encryption ===\n\n”;
int p,q,e;
cout<<"Enter first prime number (p): “;cin>>p;
for(auto c:encrypted){
long long m=modExp(c,d,n);decrypted+=char(m);cout<<c<<" -> “<<char(m)<<endl;
}
cout<<"\nDecrypted Text: “<<decrypted<<endl;
27
Output:
Learning Outcome:
Understood the working of the RSA algorithm, including key generation,
encryption, and decryption. Learned how asymmetric encryption provides
secure communication through public and private key pairs.
28
Experiment 9
Theory:
SHA-1 (Secure Hash Algorithm 1) is a cryptographic hash function designed by the
National Security Agency (NSA). It produces a fixed 160-bit (20-byte) hash value from
input data of any size. The SHA-1 algorithm is commonly used to verify data integrity and
generate digital signatures. Working Principle:
Preprocessing: The message is padded to ensure its length is congruent to 448 modulo
512,followed by appending the message length as a 64-bit value.
Processing: The padded message is divided into 512-bit blocks. Each block undergoes 80
rounds of processing involving bitwise operations, modular additions, and logical functions.
Code:
#include <iostream>
#include <iomanip>
#include <sstream>
#include <cstring>
#include <cstdint>
#include <vector>
using namespace std;
class SHA1 {
private:
static uint32_t leftRotate(uint32_t value, unsigned int count) {
return (value << count) | (value >> (32 - count));
}
public:
string hash(const string &input) {
// Initial hash values (SHA-1 standard)
uint32_t h0 = 0x67452301;
uint32_t h1 = 0xEFCDAB89;
29
uint32_t h2 = 0x98BADCFE;
uint32_t h3 = 0x10325476;
uint32_t h4 = 0xC3D2E1F0;
msg.push_back(static_cast<char>(0x80));
while (([Link]() % 64) != 56) msg.push_back(static_cast<char>(0x00));
uint32_t a = h0;
uint32_t b = h1;
uint32_t c = h2;
uint32_t d = h3;
uint32_t e = h4;
// Main loop
for (int i = 0; i < 80; ++i) {
uint32_t f, k;
if (i < 20) {
f = (b & c) | ((~b) & d);
k = 0x5A827999;
} else if (i < 40) {
f = b ^ c ^ d;
k = 0x6ED9EBA1;
} else if (i < 60) {
f = (b & c) | (b & d) | (c & d);
k = 0x8F1BBCDC;
} else {
30
f = b ^ c ^ d;
k = 0xCA62C1D6;
}
uint32_t temp = leftRotate(a, 5) + f + e + k + w[i];
e = d;
d = c;
c = leftRotate(b, 30);
b = a;
a = temp;
}
int main() {
SHA1 sha1;
string input = "Aayushbansal 23cs010";
cout << "Input: " << input << endl;
string hashValue = [Link](input);
cout << "SHA-1: " << hashValue << endl;
return 0;
}
31
Output:
Learning Outcome:
Understood the concept of cryptographic hash functions and the working of
[Link] how to generate a fixed-size hash from variable-length input
and its applications in ensuring data integrity and authentication.
32
Experiment 10
Aim:
To implement a Digital Signature Algorithm (DSA) for signing and verifying messages.
Theory:
A digital signature provides a mathematical scheme for verifying the authenticity and
integrity of digital messages or documents. Digital signatures are the asymmetric-key
equivalent of handwritten signatures and assure the recipient that the message was
created by a known sender (authentication) and was not altered in transit (integrity).
Digital Signature Algorithms (DSAs) broadly follow three steps: key generation,
signing, and verification. One widely used family is the Digital Signature Algorithm
(DSA) defined by NIST; another common approach uses RSA for signing and
verification.
Working Principle:
Key Generation: Parameters (such as prime p, subgroup q, and generator g) are
selected according to the algorithm's specifications. A private key x is chosen
randomly from the appropriate range and the corresponding public key y is
computed (for example, y = g^x mod p).
Verification: The verifier computes the hash h = H(M) and uses the signer's public
key along with r and s to check signature validity. Using algorithm-specific
computations (for DSA: w = s^{-1} mod q; u1 = h·w mod q; u2 = r·w mod q; v =
(g^{u1}·y^{u2} mod p) mod q), the signature is valid if v equals [Link] depends on
using a strong hash function, securely generating random k for eachsignature, and
protecting the private key. Digital signatures are used in secure email,software
distribution, certificates (PKI), blockchain transactions, and many authentication
protocols.
Code:
#include <iostream>
#include <cmath>
#include <cstdlib>
using namespace std;
long long modExp(long long base, long long exp, long long mod) {
long long res = 1;
base %= mod;
while (exp > 0) {
if (exp % 2 == 1)
res = (res * base) % mod;
exp /= 2;
33
base = (base * base) % mod;
}
return res;
}
int main() {
// ----- Simple hardcoded parameters -----
long long p = 23; // prime modulus
long long q = 11; // prime divisor of (p−1)
long long g = 4; // generator of order q mod p
34
else
cout << "Signature is INVALID!\n";
return 0;
}
Output:
Learning Outcome:
Understood how digital signatures provide authentication and integrity for
digital [Link] the steps of key generation, signing, and verification
in a digital signature scheme (e.g., DSA), and the importance of secure
parameter selection and randomness.
35