Skip to content

Commit 79ab7d0

Browse files
authored
Update shuffled_shift_cipher.py
1 parent d28b569 commit 79ab7d0

1 file changed

Lines changed: 67 additions & 149 deletions

File tree

ciphers/shuffled_shift_cipher.py

Lines changed: 67 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -3,182 +3,100 @@
33
import random
44
import string
55

6-
76
class ShuffledShiftCipher:
87
"""
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.
2910
"""
3011

3112
def __init__(self, passcode: str | None = None) -> None:
3213
"""
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.
3616
"""
3717
self.__passcode = passcode or self.__passcode_creator()
3818
self.__key_list = self.__make_key_list()
3919
self.__shift_key = self.__make_shift_key()
4020

4121
def __str__(self) -> str:
42-
"""
43-
:return: passcode of the cipher object
44-
"""
22+
"""Return passcode as string representation."""
4523
return "".join(self.__passcode)
4624

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
5830

5931
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."""
6933
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)]
7236

7337
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")
10341
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
11852

11953
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))
12557
return num if num > 0 else len(self.__passcode)
12658

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-
14959
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)
17970

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))
18088

18189
if __name__ == "__main__":
18290
import doctest
183-
18491
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

Comments
 (0)