Домашни > Енигма


Енигма
Краен срок: 31.10.2023 18:00
Точки: 10

### Въведение По време на Втората Световна Война един от основните методи на нацистите да водят тайна комуникация е Енигма машината. ![Enigma Design](/media/resources/enigma.png) Процесът на работа на машината е "сравнително прост". Въвеждане на буква чрез клавиатурата генерира електрически сигнал, който преминава през няколко етапа на криптиране, и води до светлинна индикация на друга буква върху циферблата. В общи линии машината конвертира една буква в друга. Конвертирането се реализира чрез минаване през следните компоненти: - Буквата за вход се чете от **клавиатурата**; - преминава през **"Plugboard"**, в който даден чифт букви *може* (но не задължително) да се свърже заедно, което води до промяна на едната буква от чифта в другата; - например "x" се свързва с "z", което значи, че ако входът е "x", изходът е "z" и обратното; - ако буквата, която влиза като вход, не е част от двойка, т.е. не е свързана с друга чрез кабел, тя не се променя; - продължава през **"Rotors"**, в който буквата, получена като резултат от предишната стъпка, преминава през поредица от три ротора, всеки от които: - променя буквата на входа на друга буква; - завърта се на една от 26 възможни позиции, което променя корелацията на вход/изход между отделните ротори. - буквата се "отразява" от края на ротора и минава по същия път, но в обратен ред; - буквата се извежда чрез светлинна индикация. Просто, нали? ### Задачата Ние не искаме да не спите, затова ще опростим дизайна. Ще имаме само един ротор и няма да "отразяваме", т.е. само минаваме през Plugboard и Rotor и извеждаме резултата. #### Напишете функция `plugboard`, която приема два позиционни аргумента: - `str` с текста, който трябва да се конвертира от плъгборда. Конвертирането се прави буква по буква през целия символен низ. - `list` от `set`-ове с по два елемента от тип `str`. Всеки сет съдържа две букви, които са свързани, т.е. вход на буква, която е част от някой `set`, ще се конвертира в съседката на тази буква от `set`-а (`set`-ът винаги съдържа две букви - едната се конвертира в другата и обратното). Функцията връща конвертиран `str`, в който всяка буква е променила стойността си спрямо дефинициите в `set`-а. #### Напишете функция `rotor`, която приема два позиционни аргумента: - `str` с текста, който трябва да се конвертира от ротора. Конвертирането се прави буква по буква през целия символен низ. - `dict` с ключове от тип `str` и стойности от тип `str`. Всеки ключ и стойност съдържат точно една буква. Речникът съдържа елементи с ключ за всяка от 26-те букви в латиницата. Всеки ключ сочи към стойност, отново една от 26-те букви в латиницата. За да бъде коректна задачата, речникът ще бъде конструиран по такъв начин, че всички 26 букви ще присъстват точно по веднъж, както в списъка от ключове, така и в списъка от стойности. Конвертирането се осъществява като всяка буква от символния низ, който трябва да се конвертира, промени стойността си спрямо речника. Всяка буква се търси в списъка от ключове на речника и променя стойността си на съответната стойност от речника, към която сочи този ключ. Функцията връща конвертиран `str`, в който всяка буква е променила стойността си спрямо дефинициите в `dict`-а. #### Напишете функция `enigma_encrypt`, която приема два именувани аргумента: - `plugboard_position` от тип `list`, който съдържа елементи от тип `set` с по два елемента от тип `str`. Това е състоянието на плъгборда на машината. Виж функцията `plugboard`. - `rotor_position` от тип `dict` с ключове от тип `str` и стойности от тип `str`. Това е състоянието на ротора на машината. Виж функцията `rotor`. Функцията връща декоратор, който може да бъде приложен на произволна функция, която очаква един позиционен аргумент от тип `str`. Декораторът трябва да криптира стринга, преди да го подаде на функцията, която е декорирал. За криптирането се използват вече дефинираните функции `plugboard` и `rotor` със съответните стойности за състоянието на компонентите от машината, които `enigma_encrypt` е получил на входа. Криптирането се извършва чрез минаване първо през плъгборда, след което през ротора. #### Напишете функция `enigma_decrypt`, която приема два именувани аргумента: - `plugboard_position` от тип `list`, който съдържа елементи от тип `set` с по два елемента от тип `str`. Това е състоянието на плъгборда на машината. Виж функцията `plugboard`. - `rotor_position` от тип `dict` с ключове от тип `str` и стойности от тип `str`. Това е състоянието на ротора на машината. Виж функцията `rotor`. Функцията връща декоратор, който може да бъде приложен на произволна функция, която очаква един позиционен аргумент от тип `str`. Декораторът трябва да ДЕкриптира стринга, преди да го подаде на функцията, която е декорирал. За декриптирането се използват вече дефинираните функции `plugboard` и `rotor` със съответните стойности за състоянието на компонентите от машината, които `enigma_dencrypt` е получил на входа. Декриптирането се извършва чрез минаване първо през ротора, след което през плъгборда. ### Уточнения Както винаги, няма да тестваме с невалиден вход. Ще се погрижим всичко, което влиза, да е валидно спрямо правилата по-горе, включително речниците за ротора да съдържат всяка буква от латиницата по точно един път и в ключовете, и в стойностите. Приемете, че ще тестваме само с малки букви от латиницата и интервали. ### Хинт Посоката на преминаване през плъгборда не е от значение, независимо дали криптирате, или ДЕкриптирате. Посоката за преминаване през ротора, обаче, е от значение. При криптиране и декриптиране се очаква различно поведение. Не опитвайте да имплементирате това поведение във функцията `rotor`. Тя има ясна и строга дефиниция. Не опитвайте да заобиколите проблема като подавате различни стойностите за `rotor_position` на `enigma_encrypt` и `enigma_decrypt`. Очаква се те винаги да работят с един и същ сет данни, който репрезентира състоянието на машината ви, както е показано в примера по-долу. Спазете тези условия и се убедете, че при тестване на кода ви, ако резултатът от криптиране на текст се декриптира, получавате оригиналния текст, т.е. машината работи! ### Примерен код ``` plugboard_position = [{'a', 'c'}, {'t', 'z'}] rotor_position = {'v': 'd', 'd': 'v', 'y': 'u', 'n': 'n', 'i': 'w', 'z': 'p', 's': 'e', 'x': 's', 'h': 'f', 'b': 'x', 'u': 'c', 'p': 'q', 'r': 'g', 'q': 'j', 'e': 't', 'l': 'y', 'o': 'z', 'g': 'o', 'k': 'b', 't': 'h', 'j': 'm', 'a': 'a', 'w': 'i', 'f': 'l', 'm': 'r', 'c': 'k'} rotor('enigma', rotor_position) # tnwora plugboard('enigma', plugboard_position) # enigmc encryptor = enigma_encrypt(plugboard_position=plugboard_position, rotor_position=rotor_position) decryptor = enigma_decrypt(plugboard_position=plugboard_position, rotor_position=rotor_position) encrypt_print = encryptor(print) decrypt_print = decryptor(print) encrypt_print('enigma') # tnwork decrypt_print('tnwork') # enigma ```
 1import types
 2import unittest
 3
 4from solution import *
 5
 6
 7PLUGBOARD_POSITION = [{'a', 'c'}, {'t', 'z'}]
 8ROTOR_POSITION = {'v': 'd', 'd': 'v', 'y': 'u', 'n': 'n', 'i': 'w', 'z': 'p',
 9                 's': 'e', 'x': 's', 'h': 'f', 'b': 'x', 'u': 'c', 'p': 'q',
10                 'r': 'g', 'q': 'j', 'e': 't', 'l': 'y', 'o': 'z', 'g': 'o',
11                 'k': 'b', 't': 'h', 'j': 'm', 'a': 'a', 'w': 'i', 'f': 'l',
12                 'm': 'r', 'c': 'k'}
13
14
15class TestPlugboard(unittest.TestCase):
16    """Test the plugboard function."""
17
18    def test_sanity(self):
19        """Sanity test for the plugboard function."""
20        self.assertIsInstance(plugboard('enigma', PLUGBOARD_POSITION), str)
21
22
23class TestRotor(unittest.TestCase):
24    """Test the rotor function."""
25
26    def test_sanity(self):
27        """Sanity test for the rotor function."""
28        self.assertIsInstance(rotor('enigma', ROTOR_POSITION), str)
29
30
31class TestEncryptor(unittest.TestCase):
32    """Test the encryptor function."""
33
34    def test_sanity(self):
35        """Sanity test for the encryptor function."""
36        self.assertIsInstance(enigma_encrypt(PLUGBOARD_POSITION, ROTOR_POSITION), types.FunctionType)
37
38
39class TestDecryptor(unittest.TestCase):
40    """Test the decryptor function."""
41
42    def test_sanity(self):
43        """Sanity test for the decryptor function."""
44        self.assertIsInstance(enigma_decrypt(PLUGBOARD_POSITION, ROTOR_POSITION), types.FunctionType)
45
46
47if __name__ == '__main__':
48    unittest.main()
  1import types
  2import unittest
  3from unittest.mock import Mock
  4
  5from solution import *
  6
  7
  8"""
  9Code for generating random rotor position
 10import random
 11
 12all_letters = []
 13for x in range(97, 97 + 26):
 14    all_letters.append(chr(x))
 15
 16keys = all_letters[:]
 17vals = all_letters[:]
 18random.shuffle(vals)
 19
 20d = {}
 21for k, v in zip(keys, vals):
 22    d[k] = v
 23
 24print(d)
 25"""
 26
 27
 28TEST_FUN = lambda x: x
 29
 30
 31class TestPlugboard(unittest.TestCase):
 32    """Test the plugboard function."""
 33
 34    def test_empty(self):
 35        """Test the plugboard function with empty input."""
 36        self.assertEqual(plugboard('', []), '')
 37
 38    def test_no_mapping(self):
 39        """Test the plugboard function with no mapping input."""
 40        self.assertEqual(plugboard('enigma machine is working', []), 'enigma machine is working')
 41
 42    def test_normal_case(self):
 43        """Test the plugboard function with normally expected input."""
 44        plugboard_position = [{'a', 'z'}, {'e', 'y'}, {'t', 'i'}]
 45        self.assertEqual(plugboard('this is a test input', plugboard_position), 'ihts ts z iysi tnpui')
 46
 47
 48class TestRotor(unittest.TestCase):
 49    """Test the rotor function."""
 50
 51    ROTOR_POSITION = {'a': 'c', 'b': 'a', 'c': 'i',
 52                      'd': 'm', 'e': 'd', 'f': 'g',
 53                      'g': 'h', 'h': 'b', 'i': 'j',
 54                      'j': 'x', 'k': 'w', 'l': 'r',
 55                      'm': 'f', 'n': 'y', 'o': 'z',
 56                      'p': 'l', 'q': 'n', 'r': 'u',
 57                      's': 'o', 't': 'k', 'u': 'q',
 58                      'v': 's', 'w': 'e', 'x': 'p',
 59                      'y': 't', 'z': 'v'}
 60
 61    def test_empty(self):
 62        """Test the rotor function with empty input."""
 63        self.assertEqual(rotor('', self.ROTOR_POSITION), '')
 64
 65    def test_normal_case(self):
 66        """Test the rotor function with normally expected input."""
 67        self.assertEqual(rotor('this is a test input', self.ROTOR_POSITION), 'kbjo jo c kdok jylqk')
 68
 69
 70class TestEncryptor(unittest.TestCase):
 71    """Test the encryptor function."""
 72
 73    PLUGBOARD_POSITION = [{'a', 'z'}, {'u', 'f'}, {'m', 'n'}, {'o', 'w'}]
 74
 75    ROTOR_POSITION = {'a': 'o', 'b': 'n', 'c': 's',
 76                      'd': 'e', 'e': 'g', 'f': 'a',
 77                      'g': 'w', 'h': 'j', 'i': 'v',
 78                      'j': 'h', 'k': 'k', 'l': 'i',
 79                      'm': 'y', 'n': 'u', 'o': 'f',
 80                      'p': 'l', 'q': 'c', 'r': 'r',
 81                      's': 'p', 't': 'm', 'u': 'z',
 82                      'v': 'x', 'w': 'q', 'x': 'd',
 83                      'y': 'b', 'z': 't'}
 84
 85    def test_full_letter_set(self):
 86        """Test the encryptor function with all letters in the rotor."""
 87        encryptor = enigma_encrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
 88        encrypted = encryptor(TEST_FUN)
 89        self.assertEqual(encrypted('the quick brown fox jumps over the lazy dog'),
 90                         'mjg cavsk nrqfy zqd haulp qxgr mjg itob eqw')
 91
 92
 93class TestDecryptor(unittest.TestCase):
 94    """Test the decryptor function."""
 95
 96    PLUGBOARD_POSITION = [{'f', 'u'}, {'g', 'q'}, {'b', 'v'}, {'r', 's'}]
 97
 98    ROTOR_POSITION = {'a': 'z', 'b': 'i', 'c': 'e',
 99                      'd': 's', 'e': 'x', 'f': 'u',
100                      'g': 'f', 'h': 'l', 'i': 'v',
101                      'j': 'g', 'k': 'r', 'l': 'p',
102                      'm': 'o', 'n': 'n', 'o': 'a',
103                      'p': 'y', 'q': 't', 'r': 'q',
104                      's': 'b', 't': 'm', 'u': 'j',
105                      'v': 'c', 'w': 'k', 'x': 'd',
106                      'y': 'w', 'z': 'h'}
107
108    def test_full_letter_set(self):
109        """Test the decryptor function with all letters in the rotor."""
110        decryptor = enigma_decrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
111        decrypted = decryptor(TEST_FUN)
112        self.assertEqual(decrypted('mlx fuver cbakn jad guoyq aixb mlx pzhw sat'),
113                         'the quick brown fox jumps over the lazy dog')
114
115
116class TestCombination(unittest.TestCase):
117    """Test decrypting an encrypted text."""
118
119    PLUGBOARD_POSITION = [{'a', 'b'}, {'c', 'd'}, {'e', 'f'}, {'g', 'h'},
120                          {'i', 'j'}, {'k', 'l'}, {'m', 'n'}, {'o', 'p'},
121                          {'q', 'r'}, {'s', 't'}, {'u', 'v'}, {'w', 'x'},
122                          {'y', 'z'}]
123
124    ROTOR_POSITION = {'a': 'z', 'b': 'i', 'c': 'e',
125                      'd': 's', 'e': 'x', 'f': 'u',
126                      'g': 'f', 'h': 'l', 'i': 'v',
127                      'j': 'g', 'k': 'r', 'l': 'p',
128                      'm': 'o', 'n': 'n', 'o': 'a',
129                      'p': 'y', 'q': 't', 'r': 'q',
130                      's': 'b', 't': 'm', 'u': 'j',
131                      'v': 'c', 'w': 'k', 'x': 'd',
132                      'y': 'w', 'z': 'h'}
133
134    def test_full_letter_set(self):
135        """Test decrypting an encrypted text against itself."""
136        encryptor = enigma_encrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
137        decryptor = enigma_decrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
138        combined = decryptor(encryptor(TEST_FUN))
139        self.assertEqual(combined('i love python'), 'i love python')
140
141
142class TestDecorators(unittest.TestCase):
143    """Test decorators."""
144
145    PLUGBOARD_POSITION = [{'a', 'b'}, {'c', 'd'}, {'e', 'f'}, {'g', 'h'},
146                          {'i', 'j'}, {'k', 'l'}, {'m', 'n'}, {'o', 'p'},
147                          {'q', 'r'}, {'s', 't'}, {'u', 'v'}, {'w', 'x'},
148                          {'y', 'z'}]
149
150    ROTOR_POSITION = {'a': 'z', 'b': 'i', 'c': 'e',
151                      'd': 's', 'e': 'x', 'f': 'u',
152                      'g': 'f', 'h': 'l', 'i': 'v',
153                      'j': 'g', 'k': 'r', 'l': 'p',
154                      'm': 'o', 'n': 'n', 'o': 'a',
155                      'p': 'y', 'q': 't', 'r': 'q',
156                      's': 'b', 't': 'm', 'u': 'j',
157                      'v': 'c', 'w': 'k', 'x': 'd',
158                      'y': 'w', 'z': 'h'}
159
160    def test_correct_decorator_order(self):
161        """Test whether the decorator is applying the functions in correct order."""
162        encryptor = enigma_encrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
163        decryptor = enigma_decrypt(self.PLUGBOARD_POSITION, self.ROTOR_POSITION)
164        mock = Mock()
165        encryptor(mock)('test')
166        mock.assert_called_once_with('bumb')
167        mock = Mock()
168        decryptor(mock)('bumb')
169        mock.assert_called_once_with('test')
170
171
172if __name__ == '__main__':
173    unittest.main()
Дискусия
Александър Ангелов
26.10.2023 16:32

позволявате ли функцията ми rotor да приема трети аргумент - флаг isDecrypt, за да е по-лесно декриптирането? ПП оправих се, намерих по-прост начин, но не ми дава да си изтрия коментара (за добро предполагам).