NAME: NAMAN SINGH
REG. NO.: 22BCE0906
Title: RSA
Ass. No.: 2
Exp. No.: 3
DATE: “8/02/2025”
Problem Statement
To implement an encryption and decryption process using RSA algorithm in
python.
Problem Description
i)
Key Generation
•
RSA key generation involves selecting two large prime numbers and
computing
their product.
•
A public and private key pair is generated using mathematical functions.
•
The public key is used for encryption, and the private key is used for
decryption.
ii)
Encryption Process
•
The sender encrypts the message using the receiver’s public key.
•
The message is converted into numerical form and raised to the power of e
modulo n.
iii)
Decryption Process
•
The receiver decrypts the ciphertext using their private key.
•
The ciphertext is raised to the power of d modulo n to retrieve the original
message.
Algorithm:
i)
Key Generation
➢
Choose two large prime numbers p and q.
➢
Compute n = p * q.
➢
Compute φ(n) = (p-1) * (q-1).
➢
Choose a public exponent e such that 1 < e < φ(n) and gcd(e, φ(n)) = 1.
➢
Compute the private exponent d such that (d * e) % φ(n) = 1.
➢
Public key: (n, e), Private key: (n, d).
ii)
Encryption Process
➢
Convert the plaintext message into numerical format.
➢
Compute ciphertext C = M^e mod n.
➢
Send the ciphertext.
iii)
Decryption Process
➢
Receive the ciphertext C.
➢
Compute plaintext M = C^d mod n.
➢
Convert M back to text format.
Program:
import random
from sympy import isprime, mod_inverse
def generate_prime(bits=8):
while True:
num = random.randint(2**(bits-1), 2**bits - 1)
if isprime(num):
return num
def generate_keys():
p = generate_prime()
q = generate_prime()
n=p*q
phi = (p-1) * (q-1)
e = 65537
d = mod_inverse(e, phi)
return ((n, e), (n, d))
def encrypt(plaintext, public_key):
n, e = public_key
numeric_message = [ord(char) for char in plaintext]
ciphertext = [pow(char, e, n) for char in numeric_message]
return ciphertext
def decrypt(ciphertext, private_key):
n, d = private_key
numeric_message = [pow(char, d, n) for char in ciphertext]
plaintext = ''.join(chr(num) for num in numeric_message)
return plaintext
message = input("Provide a message to encode: ")
bits = int(input("Define bit size for the key generation (default is 8): ") or 8)
public_key, private_key = generate_keys()
print("\n### Encryption Process ###")
print(f"Input Message: '{message}'")
ciphertext = encrypt(message, public_key)
print("Encrypted Data (Ciphertext):", ciphertext)
decrypted_message = decrypt(ciphertext, private_key)
print("\n### Decryption Process ###")
print("Recovered Message:", decrypted_message)
OUTPUT:
Title: Diffie-Hellman Key Exchange and Man-in the-MiddleAttack
ASS. NO.: 2
EXP. NO.: 4
Problem Statement
To implement the algorithms of Diffie-Hellman Key Exchange and Man-in-the-Middle
Attack
using python
Problem Description
i)
Diffie-Hellman Key Exchange
•
Diffie-Hellman Key Exchange is a method for securely exchanging cryptographic
keys over a public channel.
•
It allows two parties to generate a shared secret key without directly transmitting
it.
•
The security is based on the difficulty of computing discrete logarithms.
ii)
Man-in-the-Middle Attack
•
A Man-in-the-Middle (MITM) attack occurs when an attacker intercepts and alters
communication between two parties.
•
The attacker establishes independent connections with each party and relays
messages between them.
•
This allows the attacker to decrypt, modify, or even impersonate messages.
Algorithm:
i)
Diffie-Hellman Key Exchange
➢
Select a large prime number p and a primitive root g.
➢
Each participant selects a private key a and b.
➢
Compute public keys: A = g^a mod p and B = g^b mod p.
➢
Exchange public keys.
➢
Compute shared secret: S = B^a mod p for one party and S = A^b mod p for the
other.
➢
The shared secret S is used for encryption.
ii)
Man-in-the-Middle Attack
➢
The attacker intercepts the prime p and base g.
➢
The attacker pretends to be each party by generating their own keys.
➢
The attacker sends their public key to both parties instead of the legitimate ones.
➢
Both parties unknowingly establish a shared secret with the attacker.
➢
The attacker can decrypt, modify, and re-encrypt messages before forwarding
them.
Program:
import random
def mod_exp(base, exp, mod):
return pow(base, exp, mod)
def diffie_hellman(p, g, private_key):
public_key = mod_exp(g, private_key, p)
return public_key
def shared_secret(public_key, private_key, p):
return mod_exp(public_key, private_key, p)
def attacker(p, g, private_key):
attacker_private_key = random.randint(1, p-1)
attacker_public_key = diffie_hellman(p, g, attacker_private_key)
return attacker_private_key, attacker_public_key
p = int(input("Provide a prime number (p): "))
g = int(input("Specify base value (g): "))
a = int(input("Enter Alice's private key: "))
b = int(input("Enter Bob's private key: "))
A = diffie_hellman(p, g, a)
B = diffie_hellman(p, g, b)
attacker_A_private, attacker_A_public = attacker(p, g, a)
attacker_B_private, attacker_B_public = attacker(p, g, b)
shared_A = shared_secret(attacker_B_public, a, p)
shared_B = shared_secret(attacker_A_public, b, p)
print("\n### Key Exchange Process ###")
print(f"Computed Public Key (Alice): {A}")
print(f"Computed Public Key (Bob): {B}")
print(f"Intercepted Public Key (as Alice to Bob): {attacker_A_public}")
print(f"Intercepted Public Key (as Bob to Alice): {attacker_B_public}")
print("\n### Shared Secrets ###")
print(f"Shared Secret from Alice's View: {shared_A}")
print(f"Shared Secret from Bob's View: {shared_B}")
print(f"Malicious Attacker's Shared Secret: {shared_secret(attacker_A_public,
attacker_B_private, p)}")
OUTPUT:
NAME: NAMAN SINGH
REG. NO.: 22BCE0906
Ass. No: 1 Title: Playfair Cipher
Exp. No: Basic A
Date: “20/01/2025”
Problem Statement
To Implement a Playfair cipher encryption and decryption using python
Problem Description
i) Encryption Process
• Plaintext is split into pairs of letters.
• If a pair contains identical letters, a filler (e.g., "X") is inserted.
• If the plaintext has an odd length, a filler is added at the end.
• Encryption rules are applied based on the positions of the letters:
o Same Row: Replace each letter with the one to its right, wrapping to the start if
necessary.
o Same Column: Replace each letter with the one below it, wrapping to the top if
needed.
o Rectangle Rule: Swap letters with the ones in the opposite corners of the rectangle.
• The resulting pairs form the ciphertext.
ii) Decryption Process
• The same matrix is used for decryption.
• Ciphertext is split into pairs, and reverse rules are applied:
o Same Row: Replace each letter with the one to its left.
o Same Column: Replace each letter with the one above.
o Rectangle Rule: Swap letters with the ones in the opposite corners of the
rectangle.
• Filler letters are removed to recover the original plaintext.
Algorithm:
i) Encryption Process
• Create a 5x5 matrix using a keyword, removing duplicates and filling unused letters
(combine "I" and "J").
• Prepare plaintext by pairing letters, adding fillers for repeats or odd-length texts.
• Encrypt pairs:
• Same Row: Replace with the next letter (wrap around if needed).
• Same Column: Replace with the letter below (wrap if needed).
• Rectangle Rule: Swap with the opposite corners of the rectangle.
ii) Decryption Process
• Use the same matrix.
• Decrypt pairs:
• Same Row: Replace with the previous letter.
• Same Column: Replace with the letter above.
• Rectangle Rule: Swap with the opposite corners of the rectangle.
• Recover plaintext by removing fillers.
Program:
def toLowerCase(text):
return text.lower()
def removeSpaces(text):
return text.replace(" ", "")
def Diagraph(text):
return [text[i:i+2] for i in range(0, len(text), 2)]
def FillerLetter(text):
for i in range(0, len(text) - 1, 2):
if text[i] == text[i + 1]:
return FillerLetter(text[:i + 1] + 'x' + text[i + 1:])
return text
def generateKeyTable(word, list1):
key_letters = []
for i in word:
if i not in key_letters:
key_letters.append(i)
compElements = key_letters + [i for i in list1 if i not in key_letters]
return [compElements[i:i+5] for i in range(0, 25, 5)]
def search(mat, element):
for i in range(5):
for j in range(5):
if mat[i][j] == element:
return i, j
def encrypt_RowRule(matr, e1r, e1c, e2r, e2c):
return (matr[e1r][(e1c + 1) % 5], matr[e2r][(e2c + 1) % 5])
def encrypt_ColumnRule(matr, e1r, e1c, e2r, e2c):
return (matr[(e1r + 1) % 5][e1c], matr[(e2r + 1) % 5][e2c])
# Modified rule: Swaps positions
def encrypt_RectangleRule(matr, e1r, e1c, e2r, e2c):
return (matr[e2r][e1c], matr[e1r][e2c])
def encryptByPlayfairCipher(Matrix, plainList):
CipherText = []
for pair in plainList:
e1_x, e1_y = search(Matrix, pair[0])
e2_x, e2_y = search(Matrix, pair[1])
if e1_x == e2_x:
c1, c2 = encrypt_RowRule(Matrix, e1_x, e1_y, e2_x, e2_y)
elif e1_y == e2_y:
c1, c2 = encrypt_ColumnRule(Matrix, e1_x, e1_y, e2_x, e2_y)
else:
c1, c2 = encrypt_RectangleRule(Matrix, e1_x, e1_y, e2_x, e2_y)
CipherText.append(c1 + c2)
return CipherText
list1 = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
'z']
text_Plain = 'ciphermessage'
text_Plain = removeSpaces(toLowerCase(text_Plain))
PlainTextList = Diagraph(FillerLetter(text_Plain))
if len(PlainTextList[-1]) != 2:
PlainTextList[-1] += 'z'
key = "Security"
key = toLowerCase(key)
Matrix = generateKeyTable(key, list1)
CipherList = encryptByPlayfairCipher(Matrix, PlainTextList)
CipherText = "".join(CipherList)
print("Key text:", key)
print("Plain Text:", text_Plain)
print("CipherText:", CipherText)
Output:
Ass. No: 1 Title: Hill Cipher
Exp. No: Basic B
Problem Statement
To implement a hill cipher encryption and decryption using python
Problem Description
i) Encryption Process
• The plaintext is divided into blocks of size corresponding to the key matrix (e.g., 2 letters for
a 2x2 matrix, 3 letters for a 3x3 matrix).
• Each block is converted into a column vector of numbers (with A=0, B=1, C=2, ..., Z=25).
• The key matrix is multiplied by the plaintext vector modulo 26 to produce an encrypted
vector.
• The resulting encrypted vector is converted back into letters to form the ciphertext.
ii) Decryption Process
• The inverse of the key matrix (modulo 26) is calculated.
• The ciphertext is divided into blocks of the same size as the key matrix.
• Each block of ciphertext is converted into a column vector.
• The inverse key matrix is multiplied by the ciphertext vector modulo 26 to recover the
plaintext vector.
• The resulting plaintext vector is converted back into letters to reveal the original
message.
Algorithm:
i) Encryption Process
1. Create a Key Matrix:
o Choose a square matrix (e.g., 2x2 or 3x3) with a determinant that is invertible
modulo 26.
2. Prepare Plaintext:
o Convert the plaintext into numerical equivalents (A=0, B=1, ..., Z=25).
o Divide the plaintext into blocks matching the size of the key matrix, adding
fillers if needed.
3. Encrypt Blocks:
o Multiply each plaintext block (vector) by the key matrix.
o Compute the result modulo 26 to get the ciphertext.
ii) Decryption Process
1. Find the Inverse Key Matrix:
o Calculate the inverse of the key matrix modulo 26.
2. Decrypt Blocks:
o Multiply each ciphertext block by the inverse key matrix.
o Compute the result modulo 26 to get the plaintext.
3. Recover Plaintext:
o Convert numerical values back to letters and remove any fillers.
Program:
keyMatrix = [[0] * 3 for i in range(3)]
messageVector = [[0] for i in range(3)]
cipherMatrix = [[0] for i in range(3)]
def getKeyMatrix(key):
k=0
for i in range(3):
for j in range(3):
keyMatrix[i][j] = ord(key[k]) % 65
k += 1
def encrypt(messageVector):
for i in range(3):
for j in range(1):
cipherMatrix[i][j] = 0
for x in range(3):
cipherMatrix[i][j] += (keyMatrix[i][x] *
messageVector[x][j])
cipherMatrix[i][j] = cipherMatrix[i][j] % 26
def HillCipher(message, key):
getKeyMatrix(key)
for i in range(3):
messageVector[i][0] = ord(message[i]) % 65
encrypt(messageVector)
CipherText = []
for i in range(3):
CipherText.append(chr(cipherMatrix[i][0] + 65))
print(f"Original Message: {message}")
print(f"Encryption Key: {key}")
print(f"Ciphertext: {''.join(CipherText)}")
def main():
# Modify the message and key here
message = "HEL"
key = "DGKBNQLRP"
HillCipher(message, key)
if __name__ == "__main__":
main()
Output:
Ass. No: 1 Title: Data Encryption Standard (DES)
Exp. No: 1
Problem Statement:
To find the output of the initial permutation box, Final permutation box and S-box using
python
i) Initial permutation box and Final Permutation Box
Problem Description
• Initial Permutation Box (IP):
• The IP is the first step in many block ciphers, such as the Data Encryption Standard
(DES).
• It is a predefined table that rearranges the bits of the input data before any encryption
or transformation takes place.
• The input data (usually 64-bit) is permuted according to the IP table to scramble the
bit positions.
• The purpose is to diffuse the input data, making it harder to analyze by disrupting the
original structure.
• The result after applying the IP is passed to the next stages of the cipher for further
processing (e.g., rounds of substitution and permutation).
• Final Permutation Box (FP):
• The FP is the last step in the cipher process, typically applied after the encryption
rounds.
• It is a predefined table that performs the inverse of the IP, rearranging the bits back
into their original positions.
• The output of the final permutation is the ciphertext, which is then ready for
transmission or storage.
• The FP essentially undoes the changes made by the initial permutation, ensuring the
ciphertext is correctly formatted after all encryption steps.
• The purpose of the FP is to restore the final data arrangement after all encryption
operations have been completed, but without undoing the scrambling done by the
earlier encryption rounds.
Algorithm:
Initial Permutation Algorithm:
1. Input: A 64-bit block of data (plaintext).
2. Action:
o
Use the predefined IP table (a fixed permutation of the bits) to rearrange the
64 bits.
o The permutation table indicates the new positions for each of the original 64
bits.
3. Process:
o For each bit in the input block, find its corresponding new position in the IP
table.
o Move the bits from their original positions to the new positions based on the
IP table.
4. Output: A permuted 64-bit block of data that is sent to the next stage of the cipher.
Final Permutation Algorithm:
1. Input: A 64-bit block of data (the output of the encryption rounds).
2. Action:
o Use the predefined FP table (which is the inverse of the IP table) to rearrange
the 64 bits.
o The FP table indicates the new positions for each of the bits in the input block.
3. Process:
o For each bit in the input block, find its corresponding new position in the FP
table.
o Move the bits from their original positions to the new positions based on the
FP table.
Program:
IP_TABLE = [
58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7
]
FP_TABLE = [
40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25
]
def permute(input_bits, table):
return ''.join(input_bits[table[i] - 1] for i in range(len(table)))
def main():
# Changed input data
input_data = "1111000011110000111100001111000011110000111100001111000011110000"
print("Input Data: ", input_data)
permuted_data = permute(input_data, IP_TABLE)
print("After Initial Permutation (IP):", permuted_data)
final_data = permute(permuted_data, FP_TABLE)
print("After Final Permutation (FP): ", final_data)
if __name__ == "__main__":
main()
Output:
ii) S - Box
Problem Description
• Definition:
• An S-Box is a fundamental component in symmetric key cryptography, especially in
block ciphers like DES and AES.
• It performs a non-linear substitution of input bits to introduce confusion, which is
crucial for cryptographic security.
• Structure:
• An S-Box is typically represented as a lookup table.
• It takes a fixed-size input (e.g., 6 bits in DES or 8 bits in AES) and produces a fixed-
size output (e.g., 4 bits in DES or 8 bits in AES).
• The table maps each possible input value to a unique output value.
Algorithm:
• Input:
• A 48-bit binary value generated by the Expansion Permutation in a DES round.
• 8 S-Boxes, each having a size of 6×46 \times 46×4 (6-bit input, 4-bit output).
• Divide Input:
• Split the 48-bit input into eight 6-bit blocks (B1,B2,...,B8B_1, B_2, ..., B_8B1,B2
,...,B8).
• Each 6-bit block corresponds to one of the 8 S-Boxes.
• Process Each 6-bit Block: For each 6-bit block BiB_iBi:
• Determine the Row:
o The first and last bits of the block form a 2-bit binary value.
o Convert this 2-bit binary value to a decimal number to get the row index.
• Determine the Column:
o The middle 4 bits (bits 2 through 5) form a binary value.
o Convert this 4-bit binary value to a decimal number to get the column index.
• Lookup the S-Box:
o Use the row and column indices to look up the corresponding 4-bit output
value in the S-Box table.
• Concatenate Results:
• Collect the 4-bit outputs from all 8 S-Boxes in order.
• Concatenate them to form a single 32-bit binary value.
• Output:
• The resulting 32-bit binary value is the output of the S-Box substitution step, which is
passed to the Permutation (P-Box) step in the DES round.
Program:
def s_box_substitution(input_bits, s_boxes):
blocks = [input_bits[i:i+6] for i in range(0, len(input_bits), 6)]
output = ""
for i, block in enumerate(blocks, start=1):
if i not in s_boxes:
output += block # Keep unchanged if no S-box is applied
continue
s_box = s_boxes[i]
row = int(block[0] + block[5], 2)
col = int(block[1:5], 2)
substituted = format(s_box[row][col], '04b')
output += substituted
return output
# Example S-boxes
S1 = [
[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]
]
S3 = [
[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
[13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1],
[13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7],
[1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12]
]
S7 = [
[4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1],
[13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6],
[1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2],
[6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12]
]
# Modified input and S-box mappings
input_bits = "111100101010111010101110110011001010110101011011"
s_boxes = {1: S1, 3: S3, 7: S7}
output_bits = s_box_substitution(input_bits, s_boxes)
# Print results
print(f"Input: {input_bits}")
print(f"Output: {output_bits}")
Output:
Ass. No: 1 Title: Advanced Encryption Standard (AES)-128
Exp. No: 2
Problem Statement:
To find the output of the Mix column operation and generate the Key for Round1, Round2 and
Round3 from the Master key using python
i) Mix column Operation
Problem Description
• Input: The current state matrix (4x4 bytes).
• Transformation: Each column is treated as a polynomial over GF(28)GF(2^8)GF(28) and
multiplied by a fixed polynomial matrix:
• Result: The operation produces a new column in the transformed state matrix.
Algorithm:
• Operates on each column of the 4x4 state matrix.
• Each column is multiplied by a fixed matrix in the finite field GF(28)GF(2^8)GF(28) to
mix the bytes, ensuring diffusion.
Program:
def mix_columns(state):
fixed_matrix = [
[0x02, 0x03, 0x01, 0x01],
[0x01, 0x02, 0x03, 0x01],
[0x01, 0x01, 0x02, 0x03],
[0x03, 0x01, 0x01, 0x02]
]
new_state = []
for col in range(4):
new_column = []
for row in range(4):
val = 0
for i in range(4):
val ^= gf_multiply(fixed_matrix[row][i], state[i][col])
new_column.append(val)
new_state.append(new_column)
return new_state
def gf_multiply(a, b):
p=0
for _ in range(8):
if b & 1:
p ^= a
hi_bit_set = a & 0x80
a <<= 1
if hi_bit_set:
a ^= 0x1B
b >>= 1
return p if p < 0x80 else p ^ 0x11B
# Modified state matrix for a different result
state_matrix = [
[0x57, 0x83, 0x1e, 0x9f],
[0x1a, 0x8c, 0x36, 0x5f],
[0x2b, 0x51, 0x8d, 0x3a],
[0x9d, 0x0f, 0x7b, 0x3d]
]
new_state = mix_columns(state_matrix)
for row in new_state:
print(row)
Output:
ii) Key Expansion Algorithm
Problem Description
Key expansion derives a series of round keys from the master key. AES defines three key
lengths (128-bit, 192-bit, 256-bit). The process involves:
1. SubWord: Apply the AES S-box to each byte of a word.
2. RotWord: Rotate a word (4 bytes) left by 1 byte.
3. Rcon: XOR with a round constant.
4. Iteration: Generate new keys based on the master key using the above steps.
Algorithm:
• Input: The 128-bit master key (16 bytes).
• Steps:
• The master key is split into 4 words (each 4 bytes).
• For each new word, the previous word is used to derive it by applying a SubWord (S-
box substitution), RotWord (rotating the word), and Rcon (round constant XOR).
• After every 4th word, the SubWord and RotWord are applied, and the round
constant (Rcon) is XORed.
• New words are XORed with the previous words to generate the next set of round
keys.
• Output: An expanded key schedule that contains round keys for each round of
AES.
Program:
s_box = [
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa8, 0x51, 0xa3,
0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c,
0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81,
0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32,
0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0x0f, 0x61, 0x7a, 0x71,
0x1f, 0x1f, 0x3f, 0x3f, 0x7f, 0x7f
]
Rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]
def key_expansion(master_key):
key_schedule = [master_key[i:i+4] for i in range(0, len(master_key), 4)]
for i in range(4, 4 * (10 + 1)): # AES-128 => 11 round keys
temp = key_schedule[i - 1]
if i % 4 == 0:
temp = [
s_box[byte] for byte in [temp[1], temp[2], temp[3], temp[0]] # SubWord + RotWord
]
temp[0] ^= Rcon[i // 4 - 1] # XOR with Rcon
key_schedule.append([
key_schedule[i - 4][j] ^ temp[j] for j in range(4)
])
return key_schedule
master_key = [0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
0xab, 0xf7, 0xcf, 0xc9, 0x1f, 0x84, 0x03, 0x33]
round_keys = key_expansion(master_key)
for i in range(1, 4):
round_key = round_keys[4*i:4*(i+1)]
print(f"Round {i} Key: {[hex(byte) for word in round_key for byte in word]}")
Output: