From 17f5771dfb45228fd3d836269302eeeda331935c Mon Sep 17 00:00:00 2001 From: Ankur Chattopadhyay Date: Wed, 23 Oct 2019 10:41:53 +0530 Subject: [PATCH 1/5] introduced shuffled_shift_cipher.py in /ciphers --- ciphers/shuffled_shift_cipher.py | 186 +++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 ciphers/shuffled_shift_cipher.py diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py new file mode 100644 index 000000000000..e84ce437a10c --- /dev/null +++ b/ciphers/shuffled_shift_cipher.py @@ -0,0 +1,186 @@ +import random +import string + + +class shuffled_shift_cipher(object): + """ + This algorithm uses the Caeser Cipher algorithm but removes the option to + use brute force to decrypt the message. + + The passcode is a a random password from the selection buffer of + 1. uppercase letters of the English alphabet + 2. lowercase letters of the English alphabet + 3. digits from 0 to 9 + + Using unique characters from the passcode, the normal list of characters, + that can be allowed in the plaintext, is pivoted and shuffled. Refer to docstring + of __make_key_list() to learn more about the shuffling. + + Then, using the passcode, a number is calculated which is used to encrypt the + plaintext message with the normal shift cipher method, only in this case, the + reference, to look back at while decrypting, is shuffled. + + Each cipher object can possess an optional argument as passcode, without which a + new passcode is generated for that object automatically. + cip1 = shuffled_shift_cipher('d4usr9TWxw9wMD') + cip2 = shuffled_shift_cipher() + """ + + def __init__(self, passkey=None): + """ + Initializes a cipher object with a passcode as it's entity + Note: No new passcode is generated if user provides a passcode + while creating the object + """ + if passkey == None: + self.__passcode = self.__passcode_creator() + else: + self.__passcode = passkey + self.__key_list = self.__make_key_list() + self.__shift_key = self.__make_shift_key() + + def __str__(self): + """ + :return: passcode of the cipher object + """ + return "Passcode is: " + "".join(self.__passcode) + + def __sum_of_digits(self, num): + """ + Calculates the sum of all digits in 'num' + + :param num: a positive natural number + :return: an integer which stores the sum of digits + """ + sum_ = sum(map(int, str(num))) + return sum_ + + def __make_one_digit(self, digit): + """ + Implements an algorithm to return a single digit integer + Doesn't keep the value of input 'digit' intact + + :param digit: takes in a positive number + :return: the number itself; if its single digit + else, converts to single digit and returns + """ + while digit > 10: + digit = self.__sum_of_digits(digit) + return digit + + def __neg_pos(self, iterlist): + """ + Mutates the list by changing the sign of each alternate element + + :param iterlist: takes a list iterable + :return: the mutated list + """ + for i in range(1, len(iterlist), 2): + iterlist[i] *= -1 + return iterlist + + def __passcode_creator(self): + """ + Creates a random password from the selection buffer of + 1. uppercase letters of the English alphabet + 2. lowercase letters of the English alphabet + 3. digits from 0 to 9 + + :rtype: list + :return: a password of a random length between 10 to 20 + """ + choices = string.ascii_letters + string.digits + password = [random.choice(choices) for i in range(random.randint(10, 20))] + return password + + def __make_key_list(self): + """ + Shuffles the ordered character choices by pivoting at breakpoints + Breakpoints are the set of characters in the passcode + + eg: + if, ABCDEFGHIJKLMNOPQRSTUVWXYZ are the possible characters + and CAMERA is the passcode + then, breakpoints = [A,C,E,M,R] # sorted set of characters from passcode + shuffled parts: [A,CB,ED,MLKJIHGF,RQPON,ZYXWVUTS] + shuffled __key_list : ACBEDMLKJIHGFRQPONZYXWVUTS + + Shuffling only 26 letters of the english alphabet can generate 26! + combinations for the shuffled list. In the program we consider, a set of + 97 characters (including letters, digits, punctuation and whitespaces), + thereby creating a possibility of 97! combinations (which is a 152 digit number in itself), + thus diminishing the possibility of a brute force approach. Moreover, + shift keys even introduce a multiple of 26 for a brute force approach + for each of the already 97! combinations. + """ + # key_list_options contain nearly all printable except few elements from string.whitespace + key_list_options = ( + string.ascii_letters + string.digits + string.punctuation + " \t\n" + ) + + keys_l = [] + + # creates points known as breakpoints to break the key_list_options at those points and pivot each substring + breakpoints = sorted(set(self.__passcode)) + temp_list = [] + + # algorithm for creating a new shuffled list, keys_l, out of key_list_options + for i in key_list_options: + temp_list.extend(i) + + # checking breakpoints at which to pivot temporary sublist and add it into keys_l + if i in breakpoints or i == key_list_options[-1]: + keys_l.extend(temp_list[::-1]) + temp_list = [] + + # returning a shuffled keys_l to prevent brute force guessing of shift key + return keys_l + + def __make_shift_key(self): + """ + sum() of the mutated list of ascii values of all characters where the + mutated list is the one returned by __neg_pos() + """ + num = sum(self.__neg_pos(list(map(ord, self.__passcode)))) + return num if num > 0 else len(self.__passcode) + + def decrypt(self, encoded_message): + """ + Performs shifting of the encoded_message w.r.t. the shuffled __key_list + to create the decoded_message + """ + decoded_message = "" + + # decoding shift like caeser cipher algorithm implementing negative shift or reverse shift or left shift + for i in encoded_message: + position = self.__key_list.index(i) + decoded_message += self.__key_list[ + (position - self.__shift_key) % -len(self.__key_list) + ] + + return decoded_message + + def encrypt(self, plaintext): + """ + Performs shifting of the plaintext w.r.t. the shuffled __key_list + to create the encoded_message + """ + encoded_message = "" + + # encoding shift like caeser cipher algorithm implementing positive shift or forward shift or right shift + for i in plaintext: + position = self.__key_list.index(i) + encoded_message += self.__key_list[ + (position + self.__shift_key) % len(self.__key_list) + ] + + return encoded_message + + +if __name__ == "__main__": + # cip1 = shuffled_shift_cipher('d4usr9TWxw9wMD') + cip1 = shuffled_shift_cipher() + ciphertext = cip1.encrypt("Hello, this is a modified caeser cipher") + print(ciphertext) + print(cip1) + print(cip1.decrypt(ciphertext)) From 70e667df29229e7c54e1d244c8b8981f71981295 Mon Sep 17 00:00:00 2001 From: Ankur Chattopadhyay Date: Wed, 23 Oct 2019 20:08:24 +0530 Subject: [PATCH 2/5] made requested changes --- ciphers/shuffled_shift_cipher.py | 39 +++++++++++--------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index e84ce437a10c..a48ec0e93372 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -2,9 +2,9 @@ import string -class shuffled_shift_cipher(object): +class ShuffledShiftCipher(object): """ - This algorithm uses the Caeser Cipher algorithm but removes the option to + This algorithm uses the Caesar Cipher algorithm but removes the option to use brute force to decrypt the message. The passcode is a a random password from the selection buffer of @@ -22,20 +22,17 @@ class shuffled_shift_cipher(object): Each cipher object can possess an optional argument as passcode, without which a new passcode is generated for that object automatically. - cip1 = shuffled_shift_cipher('d4usr9TWxw9wMD') - cip2 = shuffled_shift_cipher() + cip1 = ShuffledShiftCipher('d4usr9TWxw9wMD') + cip2 = ShuffledShiftCipher() """ - def __init__(self, passkey=None): + def __init__(self, passcode=None): """ Initializes a cipher object with a passcode as it's entity Note: No new passcode is generated if user provides a passcode while creating the object """ - if passkey == None: - self.__passcode = self.__passcode_creator() - else: - self.__passcode = passkey + self.__passcode = passcode or self.__passcode_creator() self.__key_list = self.__make_key_list() self.__shift_key = self.__make_shift_key() @@ -45,16 +42,6 @@ def __str__(self): """ return "Passcode is: " + "".join(self.__passcode) - def __sum_of_digits(self, num): - """ - Calculates the sum of all digits in 'num' - - :param num: a positive natural number - :return: an integer which stores the sum of digits - """ - sum_ = sum(map(int, str(num))) - return sum_ - def __make_one_digit(self, digit): """ Implements an algorithm to return a single digit integer @@ -65,7 +52,7 @@ def __make_one_digit(self, digit): else, converts to single digit and returns """ while digit > 10: - digit = self.__sum_of_digits(digit) + digit = sum(int(x) for x in str(digit)) return digit def __neg_pos(self, iterlist): @@ -141,7 +128,7 @@ def __make_shift_key(self): sum() of the mutated list of ascii values of all characters where the mutated list is the one returned by __neg_pos() """ - num = sum(self.__neg_pos(list(map(ord, self.__passcode)))) + num = sum(self.__neg_pos([ord(x) for x in self.__passcode])) return num if num > 0 else len(self.__passcode) def decrypt(self, encoded_message): @@ -151,7 +138,7 @@ def decrypt(self, encoded_message): """ decoded_message = "" - # decoding shift like caeser cipher algorithm implementing negative shift or reverse shift or left shift + # decoding shift like Caesar cipher algorithm implementing negative shift or reverse shift or left shift for i in encoded_message: position = self.__key_list.index(i) decoded_message += self.__key_list[ @@ -167,7 +154,7 @@ def encrypt(self, plaintext): """ encoded_message = "" - # encoding shift like caeser cipher algorithm implementing positive shift or forward shift or right shift + # encoding shift like Caesar cipher algorithm implementing positive shift or forward shift or right shift for i in plaintext: position = self.__key_list.index(i) encoded_message += self.__key_list[ @@ -178,9 +165,9 @@ def encrypt(self, plaintext): if __name__ == "__main__": - # cip1 = shuffled_shift_cipher('d4usr9TWxw9wMD') - cip1 = shuffled_shift_cipher() - ciphertext = cip1.encrypt("Hello, this is a modified caeser cipher") + # cip1 = ShuffledShiftCipher('d4usr9TWxw9wMD') + cip1 = ShuffledShiftCipher() + ciphertext = cip1.encrypt("Hello, this is a modified Caesar cipher") print(ciphertext) print(cip1) print(cip1.decrypt(ciphertext)) From 95fe81050eb9dec21af6462c43e4ab65c0c1869f Mon Sep 17 00:00:00 2001 From: Ankur Chattopadhyay Date: Wed, 23 Oct 2019 21:03:32 +0530 Subject: [PATCH 3/5] introduced doctests, type hints removed __make_one_digit() --- ciphers/shuffled_shift_cipher.py | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index a48ec0e93372..bd661558d977 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -26,7 +26,7 @@ class ShuffledShiftCipher(object): cip2 = ShuffledShiftCipher() """ - def __init__(self, passcode=None): + def __init__(self, passcode: str = None): """ Initializes a cipher object with a passcode as it's entity Note: No new passcode is generated if user provides a passcode @@ -42,31 +42,19 @@ def __str__(self): """ return "Passcode is: " + "".join(self.__passcode) - def __make_one_digit(self, digit): - """ - Implements an algorithm to return a single digit integer - Doesn't keep the value of input 'digit' intact - - :param digit: takes in a positive number - :return: the number itself; if its single digit - else, converts to single digit and returns - """ - while digit > 10: - digit = sum(int(x) for x in str(digit)) - return digit - - def __neg_pos(self, iterlist): + def __neg_pos(self, iterlist: list) -> list: """ Mutates the list by changing the sign of each alternate element :param iterlist: takes a list iterable :return: the mutated list + """ for i in range(1, len(iterlist), 2): iterlist[i] *= -1 return iterlist - def __passcode_creator(self): + def __passcode_creator(self) -> list: """ Creates a random password from the selection buffer of 1. uppercase letters of the English alphabet @@ -80,7 +68,7 @@ def __passcode_creator(self): password = [random.choice(choices) for i in range(random.randint(10, 20))] return password - def __make_key_list(self): + def __make_key_list(self) -> list: """ Shuffles the ordered character choices by pivoting at breakpoints Breakpoints are the set of characters in the passcode @@ -123,7 +111,7 @@ def __make_key_list(self): # returning a shuffled keys_l to prevent brute force guessing of shift key return keys_l - def __make_shift_key(self): + def __make_shift_key(self) -> int: """ sum() of the mutated list of ascii values of all characters where the mutated list is the one returned by __neg_pos() @@ -131,10 +119,15 @@ def __make_shift_key(self): num = sum(self.__neg_pos([ord(x) for x in self.__passcode])) return num if num > 0 else len(self.__passcode) - def decrypt(self, encoded_message): + def decrypt(self, encoded_message: str) -> str: """ Performs shifting of the encoded_message w.r.t. the shuffled __key_list to create the decoded_message + + >>> ssc = ShuffledShiftCipher('4PYIXyqeQZr44') + >>> ssc.decrypt("d>**-1z6&'5z'5z:z+-='$'>=zp:>5:#z<'.&>#") + 'Hello, this is a modified Caesar cipher' + """ decoded_message = "" @@ -147,10 +140,15 @@ def decrypt(self, encoded_message): return decoded_message - def encrypt(self, plaintext): + def encrypt(self, plaintext: str) -> str: """ Performs shifting of the plaintext w.r.t. the shuffled __key_list to create the encoded_message + + >>> ssc = ShuffledShiftCipher('4PYIXyqeQZr44') + >>> ssc.encrypt('Hello, this is a modified Caesar cipher') + "d>**-1z6&'5z'5z:z+-='$'>=zp:>5:#z<'.&>#" + """ encoded_message = "" @@ -165,9 +163,11 @@ def encrypt(self, plaintext): if __name__ == "__main__": - # cip1 = ShuffledShiftCipher('d4usr9TWxw9wMD') - cip1 = ShuffledShiftCipher() - ciphertext = cip1.encrypt("Hello, this is a modified Caesar cipher") - print(ciphertext) - print(cip1) - print(cip1.decrypt(ciphertext)) + # cip1 = ShuffledShiftCipher() + # ciphertext = cip1.encrypt("Hello, this is a modified Caesar cipher") + # print(ciphertext) + # print(cip1) + # print(cip1.decrypt(ciphertext)) + import doctest + + doctest.testmod() From 994aad70f551102ae60c789d18cb51f4d3a9b91b Mon Sep 17 00:00:00 2001 From: Ankur Chattopadhyay Date: Wed, 23 Oct 2019 22:39:14 +0530 Subject: [PATCH 4/5] test_end_to_end() inserted --- ciphers/shuffled_shift_cipher.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index bd661558d977..f39a06951a59 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -162,12 +162,16 @@ def encrypt(self, plaintext: str) -> str: return encoded_message +def test_end_to_end(msg: str = "Hello, this is a modified Caesar cipher"): + """ + >>> test_end_to_end() + """ + cip1 = ShuffledShiftCipher() + ciphertext = cip1.encrypt(msg) + cip1.decrypt(ciphertext) + + if __name__ == "__main__": - # cip1 = ShuffledShiftCipher() - # ciphertext = cip1.encrypt("Hello, this is a modified Caesar cipher") - # print(ciphertext) - # print(cip1) - # print(cip1.decrypt(ciphertext)) import doctest doctest.testmod() From 70f10623962129437f9ec62f709b49adeaaff62b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 23 Oct 2019 20:01:45 +0200 Subject: [PATCH 5/5] Make test_end_to_end() a test ;-) --- ciphers/shuffled_shift_cipher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ciphers/shuffled_shift_cipher.py b/ciphers/shuffled_shift_cipher.py index f39a06951a59..bbefe3305fa7 100644 --- a/ciphers/shuffled_shift_cipher.py +++ b/ciphers/shuffled_shift_cipher.py @@ -165,10 +165,10 @@ def encrypt(self, plaintext: str) -> str: def test_end_to_end(msg: str = "Hello, this is a modified Caesar cipher"): """ >>> test_end_to_end() + 'Hello, this is a modified Caesar cipher' """ cip1 = ShuffledShiftCipher() - ciphertext = cip1.encrypt(msg) - cip1.decrypt(ciphertext) + return cip1.decrypt(cip1.encrypt(msg)) if __name__ == "__main__":