TheAlgorithms/Python · #11785
[Feature] Implemented DES Algorithm in ECB mode
DIRECTORY.md1 + / 0 −
@@ -155,6 +155,7 @@ * [Columnar Transposition](ciphers/columnar_transposition.py) * [Cryptomath Module](ciphers/cryptomath_module.py) * [Decrypt Caesar With Chi Squared](ciphers/decrypt_caesar_with_chi_squared.py)+ * [Des Ecb](ciphers/des_ecb.py) * [Deterministic Miller Rabin](ciphers/deterministic_miller_rabin.py) * [Diffie](ciphers/diffie.py) * [Diffie Hellman](ciphers/diffie_hellman.py)ciphers/des_ecb.pyadded557 + / 0 −
@@ -0,0 +1,557 @@+"""+Python program for DES (Data Encryption Standard) using Electronic Codebook (ECB) mode.++DES is a symmetric-key block cipher that encrypts data in fixed-size blocks (64 bits).+In ECB mode, the plaintext is divided into 64-bit blocks, and each block is encrypted+independently using the same key. This makes ECB the simplest block cipher mode, but+also one of the least secure, as identical plaintext blocks will produce identical+ciphertext blocks.++This implementation of DES includes key scheduling, encryption, and decryption.+It uses standard DES operations such as initial and final permutations, expansion,+permutation, and S-box lookups. Padding is applied to ensure the plaintext length is+a multiple of 64 bits.++Warning: ECB mode is not secure for most use cases due to its vulnerability to block+repetition analysis. Consider using a more secure mode of operation, such as CBC+(Cipher Block Chaining), for sensitive data encryption.++References:+- Handbook of Applied Cryptography (Algorithm 7.82)+- Handbook of Applied Cryptography (Algorithm 7.83)+- Handbook of Applied Cryptography (Algorithm 9.29)+- https://en.wikipedia.org/wiki/Data_Encryption_Standard+"""++import random++# fmt: off++# Initial Permutation Table+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 Table+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 Table+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]++# 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 (Substitution Boxes)+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]],++ "S2": [[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]],++ "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]],++ "S4": [[7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15],+ [13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9],+ [10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4],+ [3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14]],++ "S5": [[2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9],+ [14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6],+ [4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14],+ [11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3]],++ "S6": [[12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11],+ [10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8],+ [9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6],+ [4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13]],++ "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]],++ "S8": [[13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7],+ [1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2],+ [7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8],+ [2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11]]+}++# Permuted Choice 1 Table+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]++# Permuted Choice 2 Table+PC2 = [14, 17, 11, 24, 1, 5,+ 3, 28, 15, 6, 21, 10,+ 23, 19, 12, 4, 26, 8,+ 16, 7, 27, 20, 13, 2,+ 41, 52, 31, 37, 47, 55,+ 30, 40, 51, 45, 33, 48,+ 44, 49, 39, 56, 34, 53,+ 46, 42, 50, 36, 29, 32]+# fmt: on+++class Operations:+ @staticmethod+ def string_to_bitset(string: str) -> list:+ """+ Converts a string into a list of binary digits (bitset).++ Args:+ string (str): The input string to be converted.++ Returns:+ list: A list of binary digits representing the string.++ Examples:+ >>> Operations.string_to_bitset('A')+ ['0', '1', '0', '0', '0', '0', '0', '1']+ >>> len(Operations.string_to_bitset('ab'))+ 16+ >>> Operations.string_to_bitset(' ')+ ['0', '0', '1', '0', '0', '0', '0', '0']+ """+ return list("".join(format(ord(char), "08b") for char in string))++ @staticmethod+ def pad_right_to_multiple_of_n(bitset: list, length: int) -> list:+ """+ Pads the bitset with zeros on the right until+ its length is a multiple of `length`.++ Args:+ bitset (list): A list of binary digits (as strings) to be padded.++ Returns:+ list: The padded bitset, with a length that is a multiple of n.++ Examples:+ >>> Operations.pad_right_to_multiple_of_n(['1', '0', '1'], 4)+ ['1', '0', '1', '0']+ >>> len(Operations.pad_right_to_multiple_of_n(['1'] * 64, 64)) % 64+ 0+ >>> len(Operations.pad_right_to_multiple_of_n(['0'] * 63, 64)) % 64+ 0+ """+ if len(bitset) % length != 0:+ bitset += list("0" * (length - len(bitset) % length))+ return bitset++ @staticmethod+ def pad_left_to_multiple_of_n(bitset: list, length: int) -> list:+ """+ Pads the bitset with zeros on the left until+ its length is a multiple of length.++ Args:+ bitset (list): A list of binary digits (as strings) to be padded.++ Returns:+ list: The padded bitset, with a length that is a multiple of n.++ Examples:+ >>> Operations.pad_left_to_multiple_of_n(['1', '0', '1'], 4)+ ['0', '1', '0', '1']+ >>> len(Operations.pad_left_to_multiple_of_n(['1'] * 64, 64)) % 64+ 0+ >>> len(Operations.pad_left_to_multiple_of_n(['0'] * 63, 64)) % 64+ 0+ """+ if len(bitset) % length != 0:+ bitset = list("0" * (length - len(bitset) % length)) + bitset+ return bitset++ @staticmethod+ def bitset_to_hex(bitset: list) -> str:+ """+ Converts a list of binary digits+ into its 16 digit hexadecimal representation.++ Args:+ bitset (list): A list of binary digits (as strings)\+ representing a binary number.++ Returns:+ str: The 16 digit hexadecimal representation of the binary number.++ Examples:+ >>> Operations.bitset_to_hex(['1', '0', '1', '0', '1', '1', '1', '0'])+ '00000000000000ae'+ >>> Operations.bitset_to_hex(['1', '1', '1', '1'] * 16)+ 'ffffffffffffffff'+ >>> Operations.bitset_to_hex(['0'] * 64)+ '0000000000000000'+ """+ return format(int("".join(bitset), 2), "016x")++ @staticmethod+ def hex_to_bitset(hex_string: str, left_pad: int) -> list:+ """+ Converts a hexadecimal string to a bitset+ and pads the bitset to a specified length.++ Args:+ hex_string (str): The hexadecimal string to convert.+ left_pad (int): The length to pad the bitset on the left.++ Returns:+ list: The padded bitset.++ Examples:+ >>> Operations.hex_to_bitset('ae', 8)+ ['1', '0', '1', '0', '1', '1', '1', '0']+ >>> Operations.hex_to_bitset('1f', 10)+ ['0', '0', '0', '0', '0', '1', '1', '1', '1', '1']+ """+ return Operations.pad_left_to_multiple_of_n(+ list(format(int(hex_string, 16), f"0{left_pad}b")), left_pad+ )++ @staticmethod+ def bitset_to_string(bitset: list) -> str:+ """+ Converts a bitset into a string by interpreting every 8 bits as a character.++ Args:+ bitset (list): The list of binary digits (bitset).++ Returns:+ str: The decoded string.++ Examples:+ >>> Operations.bitset_to_string(['0', '1', '0', '0', '0', '0', '0', '1'])+ 'A'+ >>> Operations.bitset_to_string(['0', '1', '0', '0', '0', '1',\+'0', '0', '0', '1', '0', '1', '0', '1', '1', '0'])+ 'DV'+ """+ return "".join(+ chr(int("".join(bitset[i : i + 8]), 2)) for i in range(0, len(bitset), 8)+ )++ @staticmethod+ def xor(bitset1: list, bitset2: list) -> list:+ """+ Applies a bitwise XOR operation between two bitsets of the same length.++ Args:+ bitset1 (list): The first bitset.+ bitset2 (list): The second bitset.++ Returns:+ list: The result of the XOR operation as a new bitset.++ Examples:+ >>> Operations.xor(['0', '1', '0', '1'], ['1', '0', '1', '1'])+ ['1', '1', '1', '0']+ >>> Operations.xor(['1', '0', '1', '0'], ['0', '0', '0', '1'])+ ['1', '0', '1', '1']+ >>> Operations.xor(['1', '0', '1', '0', '1'], ['0', '0', '0', '1'])+ Traceback (most recent call last):+ ...+ ValueError: Bitsets must be of the same length+ >>> Operations.xor(['1', '0', '1', '0'], ['0', '0', '0', '1', '1'])+ Traceback (most recent call last):+ ...+ ValueError: Bitsets must be of the same length+ """+ if len(bitset1) != len(bitset2):+ raise ValueError("Bitsets must be of the same length")+ return [str(int(bitset1[i]) ^ int(bitset2[i])) for i in range(len(bitset1))]++ @staticmethod+ def shift_left(bitset: list, position: int) -> list:+ """+ Performs a rotated left shift on a bitset by `position` positions.++ Args:+ bitset (list): The bitset to be shifted.+ n (int): The number of positions to shift.++ Returns:+ list: The left-shifted bitset.++ Examples:+ >>> Operations.shift_left(['1', '0', '0', '1'], 2)+ ['0', '1', '1', '0']+ >>> Operations.shift_left(['0', '1', '1', '1'], 1)+ ['1', '1', '1', '0']+ >>> Operations.shift_left(['0', '1', '1', '1'], 7)+ ['1', '0', '1', '1']+ """+ position = position % len(bitset)+ return bitset[position:] + bitset[:position]++ @staticmethod+ def permute(bitset: list, permutation: list) -> list:+ """+ Permutes a bitset according to a given permutation table.++ Args:+ bitset (list): The bitset to be permuted.+ permutation (list): The permutation table specifying the new order.++ Returns:+ list: The permuted bitset.++ Examples:+ >>> Operations.permute(['1', '0', '1', '0', '1', '1'], [6, 5, 4, 3, 2, 1])+ ['1', '1', '0', '1', '0', '1']+ >>> Operations.permute(['0', '1', '1', '0', '1', '0'], [3, 1, 6, 5, 4, 2])+ ['1', '0', '0', '1', '0', '1']+ >>> Operations.permute(['0', '1', '1', '0', '1', '0'], \+[3, 1, 6, 5, 4, 2, 7])+ Traceback (most recent call last):+ ...+ ValueError: Permutation values must be within the range of the bitset+ """+ if not all(0 < i <= len(bitset) for i in permutation):+ raise ValueError(+ "Permutation values must be within the range of the bitset"+ )+ return [bitset[i - 1] for i in permutation]+++class Des:+ @staticmethod+ def generate_key() -> str:+ """+ Generates a random hexadecimal key of 16 characters (64 bits).++ Returns:+ str: A random hexadecimal key.++ Examples:+ >>> key = Des.generate_key()+ >>> len(key) # Check if the key length is correct+ 16+ >>> # Ensure key only contains valid hex characters+ >>> all(c in "0123456789abcdef" for c in key)+ True+ """+ return "".join(random.choice("0123456789abcdef") for i in range(16))++ @staticmethod+ def key_schedule(key: str) -> list:+ """+ Generates 16 subkeys (round keys) from a given+ 64-bit hexadecimal key using the DES key schedule.++ Args:+ key (str): A 16-character hexadecimal string representing a 64-bit key.++ Returns:+ list: A list of 16 subkeys, each of which is a permuted bitset.++ Examples:+ >>> subkeys = Des.key_schedule('133457799BBCDFF1')+ >>> len(subkeys) # Check that 16 subkeys are generated+ 16+ >>> # Ensure each subkey is 48 bits long+ >>> all(len(subkey) == 48 for subkey in subkeys)+ True+ """+ key_bitset = Operations.hex_to_bitset(key, 64)+ key_permuted = Operations.permute(key_bitset, PC1)++ left_key = key_permuted[:28]+ right_key = key_permuted[28:]++ keys = []++ shift_list = [1, 2, 9, 16]++ for i in range(1, 17):+ if i in shift_list:+ left_key = Operations.shift_left(left_key, 1)+ right_key = Operations.shift_left(right_key, 1)+ else:+ left_key = Operations.shift_left(left_key, 2)+ right_key = Operations.shift_left(right_key, 2)+ keys.append(Operations.permute(left_key + right_key, PC2))++ return keys++ @staticmethod+ def des(keys: list, plain_bitset: list) -> list:+ """+ Encrypts a plain bitset using the provided+ subkeys with DES encryption algorithm.++ Args:+ keys (list): A list of 16 subkeys, each 48 bits long.+ plain_bitset (list): A bitset representing the plain text,\+ which should be divisible into 64-bit blocks.++ Returns:+ list: The encrypted bitset.++ Examples:+ >>> plain_bitset = Operations.string_to_bitset("Test string")+ >>> plain_bitset = Operations.pad_right_to_multiple_of_n(plain_bitset, 64)+ >>> keys = Des.key_schedule(Des.generate_key())+ >>> cipher_bitset = Des.des(keys, plain_bitset)+ >>> # The output length should be the same as the input+ >>> len(cipher_bitset) == len(plain_bitset)+ True+ >>> # Decryption should be the inverse of encryption+ >>> plain_bitset == Des.des(keys[::-1], cipher_bitset)+ True+ """++ no_of_blocks = len(plain_bitset) // 64++ cipher_bitset = []++ for i in range(no_of_blocks):+ block = plain_bitset[i * 64 : (i + 1) * 64]+ initial_permutation = Operations.permute(block, IP)++ left_bits = initial_permutation[:32]+ right_bits = initial_permutation[32:]++ for i in range(16):+ right_expanded = Operations.permute(right_bits, E)+ right_xor_key = Operations.xor(right_expanded, keys[i])+ right_s_box = []+ for j in range(8):+ s_box_input = right_xor_key[j * 6 : (j + 1) * 6]+ row = 2 * int(s_box_input[0]) + int(s_box_input[5])+ col = int("".join(s_box_input[1:5]), 2)+ s_box_output = format(S_BOXES[f"S{j + 1}"][row][col], "04b")+ right_s_box += list(s_box_output)++ right_permuted = Operations.permute(right_s_box, P)++ left_bits, right_bits = (+ right_bits,+ Operations.xor(left_bits, right_permuted),+ )++ left_bits, right_bits = right_bits, left_bits+ cipher_bitset += Operations.permute(left_bits + right_bits, IP_INV)+ return cipher_bitset++ @staticmethod+ def encrypt(key: str, input_string: str) -> str:+ """+ Encrypts a given input string using the DES encryption algorithm.++ Args:+ key (str): A 16-character hexadecimal string representing a 64-bit key.+ input_string (str): The plain text string to be encrypted.++ Returns:+ str: A hexadecimal string representing the encrypted data.++ Examples:+ >>> key = '133457799BBCDFF1'+ >>> input_string = 'Test string'+ >>> encrypted = Des.encrypt(key, input_string)+ >>> encrypted # Checking the cipher to ensure consistency+ 'c84e3c8fb646872720b224896db4f60'+ """+ plain_bitset = Operations.string_to_bitset(input_string)+ padded_bitset = Operations.pad_right_to_multiple_of_n(plain_bitset, 64)+ keys = Des.key_schedule(key)+ cipher_bitset = Des.des(keys, padded_bitset)+ return Operations.bitset_to_hex(cipher_bitset)++ @staticmethod+ def decrypt(key: str, cipher_text: str) -> str:+ """+ Decrypts the given cipher text using DES decryption.++ Args:+ key (str): A 16-character hexadecimal string representing a 64-bit key.+ cipher_text (str): A hexadecimal string representing the encrypted data.++ Returns:+ str: The decrypted plain text string.++ Examples:+ >>> key = '133457799BBCDFF1'+ >>> encrypted_string = 'c84e3c8fb646872720b224896db4f60'+ >>> decrypted = Des.decrypt(key, encrypted_string)+ >>> decrypted # Checking the cipher to ensure consistency+ 'Test string'+ """+ cipher_bitset = Operations.hex_to_bitset(cipher_text, 64)+ keys = Des.key_schedule(key)[::-1]+ plain_bitset = Des.des(keys, cipher_bitset)+ return Operations.bitset_to_string(plain_bitset).replace("\x00", "")+++if __name__ == "__main__":+ import doctest++ doctest.testmod()++ while True:+ print()+ print("################ DES Algorithm ################")+ print()+ print("Select an option:")+ print("1. To encrypt a string, enter 'e'")+ print("2. To decrypt a string, enter 'd'")+ print("3. To generate a key, enter 'k'")+ print("4. To Quit, enter 'q'")+ mode = input("Enter the option: ").strip().lower()+ if mode not in ["e", "d", "k", "q"]:+ print("Invalid option. Please try again.")+ continue+ if mode == "q":+ break+ elif mode == "k":+ print(f"Generated key: {Des.generate_key()}")+ elif mode == "e":+ key = input("Enter the key: ").strip()+ if len(key) != 16 and not all(char in "0123456789abcdef" for char in key):+ print("Invalid key. Please try again.")+ continue+ input_string = input("Enter the string to encrypt: ").strip()+ print(f"Encrypted string: {Des.encrypt(key, input_string)}")+ elif mode == "d":+ key = input("Enter the key: ").strip()+ if len(key) != 16 and not all(char in "0123456789abcdef" for char in key):+ print("Invalid key. Please try again.")+ continue+ cipher_text = input("Enter the cipher text to decrypt: ").strip()+ print(f"Decrypted string: {Des.decrypt(key, cipher_text)}")