Skip to content

Commit 418f412

Browse files
chttrjeankrstokhos
authored andcommitted
introduced shuffled_shift_cipher.py in /ciphers (TheAlgorithms#1424)
* introduced shuffled_shift_cipher.py in /ciphers * made requested changes * introduced doctests, type hints removed __make_one_digit() * test_end_to_end() inserted * Make test_end_to_end() a test ;-)
1 parent 0d5cda5 commit 418f412

File tree

1 file changed

+177
-0
lines changed

1 file changed

+177
-0
lines changed

ciphers/shuffled_shift_cipher.py

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

0 commit comments

Comments
 (0)