ASSIGNMENT – 3
3.1. SAC and BIC
Objective:
● To implement a cryptographic function and evaluate it for SAC and BIC.
● To understand how SAC and BIC contribute to cryptographic security.
Problem Statement:
● SAC Evaluation:
○ Implement a cryptographic function (e.g., a simple S-box or XOR-based
cipher).
○ Evaluate if the function satisfies the Strict Avalanche Criterion (SAC).
○ To do this, flip each bit of the input and calculate the number of bits that
change in the output. If approximately half of the output bits change for each
input bit flip, the function satisfies SAC.
• BIC Evaluation:
○ Evaluate the Bit Independence Criterion (BIC) for the same function.
○ When you flip an input bit, check if the change in one output bit is
independent of the change in another output bit. This can be done by
calculating the correlation between different output bits' changes
Source code:
import numpy as np
def xor_cipher(input_data, key):
return input_data ^ key
def evaluate_sac(func, input_bits, key):
"""Evaluate SAC of the cryptographic function."""
original_output = func(input_bits, key)
output_length = len(bin(original_output)) - 2
sac_results = []
for i in range(input_bits.bit_length()):
flipped_input = input_bits ^ (1 << i)
original_output = func(input_bits, key)
flipped_output = func(flipped_input, key)
# Count how many output bits have changed
changed_bits = bin(original_output ^ flipped_output).count('1')
sac_results.append(changed_bits)
# Check if around half of the output bits change for each input
flip
expected_change = output_length / 2
success = all(abs(change - expected_change) <= output_length * 0.1
for change in sac_results)
return success
def evaluate_bic(func, input_bits, key):
"""Evaluate BIC of the cryptographic function."""
original_output = func(input_bits, key)
output_length = len(bin(original_output)) - 2
bic_results = []
# Flip each bit of the input and check correlations between output
bits
for i in range(input_bits.bit_length()):
flipped_input = input_bits ^ (1 << i) # Flip the i-th bit
original_output = func(input_bits, key)
flipped_output = func(flipped_input, key)
# Calculate bitwise XOR between the original and flipped
outputs
diff_output = original_output ^ flipped_output
# Measure the independence between different output bits
output_bits = [(diff_output >> j) & 1 for j in
range(output_length)]
correlations = []
for j in range(output_length):
for k in range(j + 1, output_length):
# Compute correlation between the changes in two output
bits
correlation = output_bits[j] ^ output_bits[k]
correlations.append(correlation)
bic_results.append(correlations)
# Check if correlation is close to zero for bit independence (1
means dependent, 0 means independent)
success = all(np.mean(correlations) == 0 for correlations in
bic_results)
return success
input_bits = int(input("Enter the input bits (as a binary number): "),
2)
key = int(input("Enter the key (as a binary number): "), 2)
print(f"Input bits: {bin(input_bits)}")
print(f"Key: {bin(key)}")
sac_result = evaluate_sac(xor_cipher, input_bits, key)
bic_result = evaluate_bic(xor_cipher, input_bits, key)
print(f"SAC Satisfied: {sac_result}")
print(f"BIC Satisfied: {bic_result}")
Output:
Result:
SAC ensures that small changes in input create widespread changes in output,
providing unpredictability and resisting differential cryptanalysis.
BIC ensures that output bits are uncorrelated, eliminating dependencies and
improving diffusion, making attacks based on patterns more difficult.
3.2. DES:
Implement the Data Encryption Standard (DES) algorithm to understand its core components
and encryption-decryption process. You will
Develop a program that simulates the entire DES encryption scheme, including key
generation, initial permutation, 16 rounds of Feistel function, and final permutation.
Implementation should support both encryption and decryption of 64-bit blocks using
a 56-bit key.
Finally, demonstrate how DES securely encrypts and decrypts data, ensuring that the
original plaintext is restored after decryption.
Solution:
Initial Permutation (IP): The 64-bit block is rearranged using a predefined
permutation table.
Key Generation: A 56-bit key is used to generate 16 subkeys, each used in one round
of encryption.
Feistel Function: The block undergoes 16 rounds of the Feistel function, where the
block is divided into two halves, and a round function is applied to the right half and
XORed with the left half.
Final Permutation (FP): After 16 rounds, the block is permuted again to produce the
ciphertext.
Source code:
import itertools
def permute(block, table):
return [block[i - 1] for i in table]
# Initial Permutation (IP)
IP = [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]
# Final Permutation (IP^-1)
IP_INV = [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]
# Expansion permutation (E)
E = [32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1]
# P Permutation table
P = [16, 7, 20, 21, 29, 12, 28, 17,
1, 15, 23, 26, 5, 18, 31, 10,
2, 8, 24, 14, 32, 27, 3, 9,
19, 13, 30, 6, 22, 11, 4, 25]
# S-boxes
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]],
[[15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10],
[3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5],
[0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15],
[13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9]],
[[10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8],
[13, 7, 0, 9, 3, 4, 6, 10, 1, 2, 8, 5, 11, 15, 14, 12],
[14, 3, 0, 7, 9, 2, 12, 8, 15, 5, 4, 1, 10, 11, 6, 13],
[1, 15, 13, 8, 14, 10, 11, 7, 9, 4, 5, 0, 6, 12, 3, 2]],
[[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 15, 12, 4, 11],
[11, 0, 4, 12, 1, 10, 13, 15, 14, 8, 7, 9, 3, 5, 6, 2],
[8, 14, 12, 6, 11, 1, 10, 15, 9, 7, 2, 13, 3, 5, 4, 0],
[1, 10, 13, 7, 8, 0, 15, 14, 9, 11, 5, 3, 4, 2, 6, 12]],
[[4, 1, 3, 12, 7, 14, 13, 10, 15, 9, 5, 11, 8, 6, 2, 0],
[15, 5, 12, 8, 1, 6, 3, 13, 7, 11, 14, 10, 4, 0, 9, 2],
[10, 15, 2, 4, 14, 12, 13, 11, 8, 0, 9, 7, 6, 3, 1, 5],
[7, 11, 5, 9, 13, 0, 15, 3, 10, 8, 12, 14, 1, 6, 2, 4]],
[[1, 15, 13, 10, 7, 4, 11, 14, 0, 8, 9, 5, 6, 3, 12, 2],
[15, 2, 12, 4, 7, 10, 0, 13, 9, 3, 5, 8, 11, 6, 1, 14],
[3, 15, 10, 9, 0, 8, 7, 13, 5, 14, 6, 12, 11, 1, 2, 4],
[12, 6, 7, 11, 1, 14, 13, 3, 0, 15, 5, 4, 9, 8, 2, 10]],
[[4, 11, 8, 12, 7, 5, 9, 6, 1, 15, 0, 14, 10, 3, 13, 2],
[6, 3, 9, 7, 14, 2, 12, 15, 1, 5, 10, 13, 8, 4, 11, 0],
[15, 1, 7, 14, 12, 13, 5, 8, 10, 0, 4, 9, 2, 3, 11, 6],
[9, 0, 6, 3, 15, 12, 13, 8, 1, 14, 11, 10, 7, 2, 5, 4]],
[[12, 1, 10, 15, 9, 2, 6, 8, 13, 5, 3, 7, 14, 0, 11, 4],
[4, 15, 1, 11, 8, 13, 0, 9, 2, 3, 7, 6, 14, 10, 12, 5],
[1, 3, 13, 10, 11, 0, 6, 9, 14, 5, 8, 7, 15, 2, 12, 4],
[8, 7, 9, 12, 15, 4, 10, 13, 2, 3, 6, 1, 5, 11, 14, 0]],
[[7, 13, 12, 14, 8, 3, 15, 11, 10, 6, 5, 9, 1, 0, 4, 2],
[3, 12, 10, 15, 5, 9, 1, 8, 4, 14, 7, 6, 0, 13, 2, 11],
[15, 5, 3, 13, 1, 10, 9, 6, 11, 7, 12, 4, 14, 8, 0, 2],
[9, 2, 12, 4, 11, 10, 1, 15, 7, 8, 3, 5, 6, 13, 14, 0]]
]
def sbox_substitution(expanded_half):
"""Substitutes values using the S-boxes."""
output = []
for i in range(8):
block = expanded_half[i * 6:(i + 1) * 6]
row = (block[0] << 1) + block[5]
col = (block[1] << 3) + (block[2] << 2) + (block[3] << 1) +
block[4]
val = SBOX[i][row][col]
output.extend([int(b) for b in f'{val:04b}'])
return output
def feistel_function(right_half, round_key):
expanded_half = permute(right_half, E)
xor_result = [expanded_half[i] ^ round_key[i] for i in range(48)]
sbox_result = sbox_substitution(xor_result)
return permute(sbox_result, P)
def des_encrypt(block, key):
block = permute(block, IP)
left, right = block[:32], block[32:]
round_keys = [key for _ in range(16)]
for i in range(16):
temp_right = right
right = [left[j] ^ feistel_function(right, round_keys[i])[j]
for j in range(32)]
left = temp_right
combined_block = left + right
return permute(combined_block, IP_INV)
def string_to_binary(input_string):
binary_string = ''.join(format(ord(c), '08b') for c in
input_string)
binary_list = [int(bit) for bit in binary_string[:64].ljust(64,
'0')]
return binary_list
plaintext_input = input("Enter the plaintext (up to 8 characters): ")
plaintext = string_to_binary(plaintext_input)
key = [1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1,
0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1,
1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1,
0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0]
ciphertext = des_encrypt(plaintext, key)
print("Encrypted ciphertext:", ciphertext)
Output:
Result:
Data Encryption Standard (DES) is implemented successfully.