|
3 | 3 | import random |
4 | 4 | import string |
5 | 5 |
|
6 | | - |
7 | 6 | class ShuffledShiftCipher: |
8 | 7 | """ |
9 | | - This algorithm uses the Caesar Cipher algorithm but removes the option to |
10 | | - use brute force to decrypt the message. |
11 | | -
|
12 | | - The passcode is a random password from the selection buffer of |
13 | | - 1. uppercase letters of the English alphabet |
14 | | - 2. lowercase letters of the English alphabet |
15 | | - 3. digits from 0 to 9 |
16 | | -
|
17 | | - Using unique characters from the passcode, the normal list of characters, |
18 | | - that can be allowed in the plaintext, is pivoted and shuffled. Refer to docstring |
19 | | - of __make_key_list() to learn more about the shuffling. |
20 | | -
|
21 | | - Then, using the passcode, a number is calculated which is used to encrypt the |
22 | | - plaintext message with the normal shift cipher method, only in this case, the |
23 | | - reference, to look back at while decrypting, is shuffled. |
24 | | -
|
25 | | - Each cipher object can possess an optional argument as passcode, without which a |
26 | | - new passcode is generated for that object automatically. |
27 | | - cip1 = ShuffledShiftCipher('d4usr9TWxw9wMD') |
28 | | - cip2 = ShuffledShiftCipher() |
| 8 | + Enhanced Caesar Cipher with shuffled character set for stronger encryption. |
| 9 | + Uses a passcode to generate a unique shuffled key list and shift key. |
29 | 10 | """ |
30 | 11 |
|
31 | 12 | def __init__(self, passcode: str | None = None) -> None: |
32 | 13 | """ |
33 | | - Initializes a cipher object with a passcode as it's entity |
34 | | - Note: No new passcode is generated if user provides a passcode |
35 | | - while creating the object |
| 14 | + Initialize cipher with optional passcode. |
| 15 | + Generates random passcode if none provided. |
36 | 16 | """ |
37 | 17 | self.__passcode = passcode or self.__passcode_creator() |
38 | 18 | self.__key_list = self.__make_key_list() |
39 | 19 | self.__shift_key = self.__make_shift_key() |
40 | 20 |
|
41 | 21 | def __str__(self) -> str: |
42 | | - """ |
43 | | - :return: passcode of the cipher object |
44 | | - """ |
| 22 | + """Return passcode as string representation.""" |
45 | 23 | return "".join(self.__passcode) |
46 | 24 |
|
47 | | - def __neg_pos(self, iterlist: list[int]) -> list[int]: |
48 | | - """ |
49 | | - Mutates the list by changing the sign of each alternate element |
50 | | -
|
51 | | - :param iterlist: takes a list iterable |
52 | | - :return: the mutated list |
53 | | -
|
54 | | - """ |
55 | | - for i in range(1, len(iterlist), 2): |
56 | | - iterlist[i] *= -1 |
57 | | - return iterlist |
| 25 | + def __neg_pos(self, iter_list: list[int]) -> list[int]: |
| 26 | + """Alternate sign of elements in list.""" |
| 27 | + for i in range(1, len(iter_list), 2): |
| 28 | + iter_list[i] *= -1 |
| 29 | + return iter_list |
58 | 30 |
|
59 | 31 | def __passcode_creator(self) -> list[str]: |
60 | | - """ |
61 | | - Creates a random password from the selection buffer of |
62 | | - 1. uppercase letters of the English alphabet |
63 | | - 2. lowercase letters of the English alphabet |
64 | | - 3. digits from 0 to 9 |
65 | | -
|
66 | | - :rtype: list |
67 | | - :return: a password of a random length between 10 to 20 |
68 | | - """ |
| 32 | + """Generate random passcode.""" |
69 | 33 | choices = string.ascii_letters + string.digits |
70 | | - password = [random.choice(choices) for _ in range(random.randint(10, 20))] |
71 | | - return password |
| 34 | + pass_len = random.randint(10, 20) |
| 35 | + return [random.choice(choices) for _ in range(pass_len)] |
72 | 36 |
|
73 | 37 | def __make_key_list(self) -> list[str]: |
74 | | - """ |
75 | | - Shuffles the ordered character choices by pivoting at breakpoints |
76 | | - Breakpoints are the set of characters in the passcode |
77 | | -
|
78 | | - eg: |
79 | | - if, ABCDEFGHIJKLMNOPQRSTUVWXYZ are the possible characters |
80 | | - and CAMERA is the passcode |
81 | | - then, breakpoints = [A,C,E,M,R] # sorted set of characters from passcode |
82 | | - shuffled parts: [A,CB,ED,MLKJIHGF,RQPON,ZYXWVUTS] |
83 | | - shuffled __key_list : ACBEDMLKJIHGFRQPONZYXWVUTS |
84 | | -
|
85 | | - Shuffling only 26 letters of the english alphabet can generate 26! |
86 | | - combinations for the shuffled list. In the program we consider, a set of |
87 | | - 97 characters (including letters, digits, punctuation and whitespaces), |
88 | | - thereby creating a possibility of 97! combinations (which is a 152 digit number |
89 | | - in itself), thus diminishing the possibility of a brute force approach. |
90 | | - Moreover, shift keys even introduce a multiple of 26 for a brute force approach |
91 | | - for each of the already 97! combinations. |
92 | | - """ |
93 | | - # key_list_options contain nearly all printable except few elements from |
94 | | - # string.whitespace |
95 | | - key_list_options = ( |
96 | | - string.ascii_letters + string.digits + string.punctuation + " \t\n" |
97 | | - ) |
98 | | - |
99 | | - keys_l = [] |
100 | | - |
101 | | - # creates points known as breakpoints to break the key_list_options at those |
102 | | - # points and pivot each substring |
| 38 | + """Create shuffled character set using passcode breakpoints.""" |
| 39 | + # Get printable characters except rare whitespace |
| 40 | + key_options = string.printable.strip("\r\x0b\x0c") |
103 | 41 | breakpoints = sorted(set(self.__passcode)) |
104 | | - temp_list: list[str] = [] |
105 | | - |
106 | | - # algorithm for creating a new shuffled list, keys_l, out of key_list_options |
107 | | - for i in key_list_options: |
108 | | - temp_list.extend(i) |
109 | | - |
110 | | - # checking breakpoints at which to pivot temporary sublist and add it into |
111 | | - # keys_l |
112 | | - if i in breakpoints or i == key_list_options[-1]: |
113 | | - keys_l.extend(temp_list[::-1]) |
114 | | - temp_list.clear() |
115 | | - |
116 | | - # returning a shuffled keys_l to prevent brute force guessing of shift key |
117 | | - return keys_l |
| 42 | + shuffled = [] |
| 43 | + temp = [] |
| 44 | + |
| 45 | + for char in key_options: |
| 46 | + temp.append(char) |
| 47 | + if char in breakpoints or char == key_options[-1]: |
| 48 | + shuffled.extend(reversed(temp)) |
| 49 | + temp.clear() |
| 50 | + |
| 51 | + return shuffled |
118 | 52 |
|
119 | 53 | def __make_shift_key(self) -> int: |
120 | | - """ |
121 | | - sum() of the mutated list of ascii values of all characters where the |
122 | | - mutated list is the one returned by __neg_pos() |
123 | | - """ |
124 | | - num = sum(self.__neg_pos([ord(x) for x in self.__passcode])) |
| 54 | + """Calculate shift key from passcode ASCII values.""" |
| 55 | + ascii_vals = [ord(x) for x in self.__passcode] |
| 56 | + num = sum(self.__neg_pos(ascii_vals)) |
125 | 57 | return num if num > 0 else len(self.__passcode) |
126 | 58 |
|
127 | | - def decrypt(self, encoded_message: str) -> str: |
128 | | - """ |
129 | | - Performs shifting of the encoded_message w.r.t. the shuffled __key_list |
130 | | - to create the decoded_message |
131 | | -
|
132 | | - >>> ssc = ShuffledShiftCipher('4PYIXyqeQZr44') |
133 | | - >>> ssc.decrypt("d>**-1z6&'5z'5z:z+-='$'>=zp:>5:#z<'.&>#") |
134 | | - 'Hello, this is a modified Caesar cipher' |
135 | | -
|
136 | | - """ |
137 | | - decoded_message = "" |
138 | | - |
139 | | - # decoding shift like Caesar cipher algorithm implementing negative shift or |
140 | | - # reverse shift or left shift |
141 | | - for i in encoded_message: |
142 | | - position = self.__key_list.index(i) |
143 | | - decoded_message += self.__key_list[ |
144 | | - (position - self.__shift_key) % -len(self.__key_list) |
145 | | - ] |
146 | | - |
147 | | - return decoded_message |
148 | | - |
149 | 59 | def encrypt(self, plaintext: str) -> str: |
150 | | - """ |
151 | | - Performs shifting of the plaintext w.r.t. the shuffled __key_list |
152 | | - to create the encoded_message |
153 | | -
|
154 | | - >>> ssc = ShuffledShiftCipher('4PYIXyqeQZr44') |
155 | | - >>> ssc.encrypt('Hello, this is a modified Caesar cipher') |
156 | | - "d>**-1z6&'5z'5z:z+-='$'>=zp:>5:#z<'.&>#" |
157 | | -
|
158 | | - """ |
159 | | - encoded_message = "" |
160 | | - |
161 | | - # encoding shift like Caesar cipher algorithm implementing positive shift or |
162 | | - # forward shift or right shift |
163 | | - for i in plaintext: |
164 | | - position = self.__key_list.index(i) |
165 | | - encoded_message += self.__key_list[ |
166 | | - (position + self.__shift_key) % len(self.__key_list) |
167 | | - ] |
168 | | - |
169 | | - return encoded_message |
170 | | - |
171 | | - |
172 | | -def test_end_to_end(msg: str = "Hello, this is a modified Caesar cipher") -> str: |
173 | | - """ |
174 | | - >>> test_end_to_end() |
175 | | - 'Hello, this is a modified Caesar cipher' |
176 | | - """ |
177 | | - cip1 = ShuffledShiftCipher() |
178 | | - return cip1.decrypt(cip1.encrypt(msg)) |
| 60 | + """Encrypt plaintext using shuffled shift cipher.""" |
| 61 | + encoded = [] |
| 62 | + key_len = len(self.__key_list) |
| 63 | + |
| 64 | + for char in plaintext: |
| 65 | + pos = self.__key_list.index(char) |
| 66 | + new_pos = (pos + self.__shift_key) % key_len |
| 67 | + encoded.append(self.__key_list[new_pos]) |
| 68 | + |
| 69 | + return "".join(encoded) |
179 | 70 |
|
| 71 | + def decrypt(self, encoded_message: str) -> str: |
| 72 | + """Decrypt message using shuffled shift cipher.""" |
| 73 | + decoded = [] |
| 74 | + key_len = len(self.__key_list) |
| 75 | + |
| 76 | + for char in encoded_message: |
| 77 | + pos = self.__key_list.index(char) |
| 78 | + new_pos = (pos - self.__shift_key) % key_len |
| 79 | + decoded.append(self.__key_list[new_pos]) |
| 80 | + |
| 81 | + return "".join(decoded) |
| 82 | + |
| 83 | +def test_end_to_end() -> str: |
| 84 | + """Test full encryption-decryption cycle.""" |
| 85 | + msg = "Hello, this is a modified Caesar cipher" |
| 86 | + cipher = ShuffledShiftCipher() |
| 87 | + return cipher.decrypt(cipher.encrypt(msg)) |
180 | 88 |
|
181 | 89 | if __name__ == "__main__": |
182 | 90 | import doctest |
183 | | - |
184 | 91 | doctest.testmod() |
| 92 | + |
| 93 | + # Example usage |
| 94 | + cipher = ShuffledShiftCipher("SecurePass123") |
| 95 | + original = "Encryption test!" |
| 96 | + encrypted = cipher.encrypt(original) |
| 97 | + decrypted = cipher.decrypt(encrypted) |
| 98 | + |
| 99 | + print(f"Original: {original}") |
| 100 | + print(f"Encrypted: {encrypted}") |
| 101 | + print(f"Decrypted: {decrypted}") |
| 102 | + print(f"Test passed: {decrypted == original}") |
0 commit comments