TheAlgorithms/Python · #11785

[Feature] Implemented DES Algorithm in ECB mode

MSaiAswin · merged Sep 20, 20262 files · 558 + / 0
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)}")