Самописный метод шифрования на основе Энигмы и его использование на практике

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0

Введение.​

В данной статье будет представлен собственный метод шифрования на основе механизма шифровальной машины Энигма.
Эта шифровальная машина использовалась во время Второй мировой войны немцами. Над расшифровкой данных от Энигмы трудилось огромное количество людей, в основном поляков и англичан.
Больше узнать об истории расшифровки данных Энигмы и её истории в целом можно по этим ссылкам:
Как польские математики взломали Энигму / Хабр
Как британскому гению Алану Тьюрингу удалось разгадать код "Энигмы"

Почему я решил написать кастомный метод шифрования а не использовать допустим AES?​

В конкурсных статьях мною была замечена эта статья:Собственная концепция шифрования данных благодаря незрячим. | XSS.is (ex DaMaGeLaB)

Она показалась мне достаточно интересной, и так как в предыдущей статье я писал о своём холодном кошельке, я подумал, что неплохо было бы использовать своё собственное шифрование в нём, которое, возможно, улучшит его безопасность в какой-то степени, так как при получении данных вашего холодного кошелька злоумышленники будут ожидать стандартное и привычное шифрование AES GCM и, скорее всего, просто не поймут, что делать с тем, что получили на самом деле.

Где будет применен метод?​

Как и было сказано выше, метод будет использован в самописном холодном кошельке Dash, а также в качестве примера. Написанный метод шифрования будет применён в полностью новом проекте в виде простого чата между двумя пользователями с отправкой зашифрованных сообщений через сокеты.

Почему была взята за основу именно Энигма?​

По моему мнению, это одна из самых легендарных и важных историй о шифровании данных, и мне захотелось использовать её крупицу в своём проекте.

Как устроена энигма?​

Перед тем как начать писать код, необходимо разобрать устройство работы самой машины Энигма.
Для начала рассмотрим сам внешний вид Энигмы:
1740198098861.png


Сама Энигма очень похожа на печатную машинку, принцип её работы также чем-то с ней схож. Важным различием является то, что Энигма — это не полностью механическое устройство, она работает от батареи.
Основным её принципом работы является передача электрического импульса на контакт, обозначающий одну из букв английского алфавита. После передачи импульса происходит печать, но с оговоркой на то, что импульс проходит не напрямую на букву, которая была выбрана. Он проходит через несколько контактов, означающих разные буквы. Таким образом, при выборе одной буквы импульс проходит через другую и печатает, соответственно, тоже другую букву. Это краткое пояснение, которое, скорее всего, ничего вам не даст, поэтому считаю нужным объяснить устройство основного механизма более подробно.

Элементы механизма.​

Для начала нужно понять, какие элементы в ней были и за что они отвечали.

Роторы.​

Основным элементом являлись роторы. Обычно роторов было 3 штуки, но встречались машины и с 4 роторами. Каждый ротор представлял собой диск с 26 контактами, каждый контакт обозначал одну из букв английского алфавита.
1740198124338.png


Контакты у каждого ротора были с обеих сторон. Эти контакты были подключены друг к другу, но не по прямой, а в перемешку.
1740198148264.png



Также пример ротора с двух сторон и его соединение контактов:
1740198176490.png



Таким образом, когда задействован контакт, допустим, буквы A, он будет передавать импульс к соединённому с ним контакту в этом же роторе, допустим, C. В таком случае буква уже меняется. Но не забываем, что таких роторов 4 штуки, и все они соединяются друг с другом контактами.

Это означает, что подмена буквы происходит уже 4 раза (3D-модели машины я нашёл только с тремя роторами, но суть остаётся той же).
1740198214624.png


Рефлектор.​

Но кроме роторов есть ещё один немаловажный элемент — это рефлектор.
Рефлектор находится после всех роторов, на нём также есть 26 контактов, только один контакт в этом же рефлекторе подсоединён к другому контакту.
1740198260570.png



Таким образом, когда импульс при выборе конкретной буквы проходит через 4 ротора, изменяя шифрование этой буквы 4 раза, далее импульс проходит к рефлектору, который в свою очередь возвращает импульс обратно в роторы, но уже по другим контактам. Таким образом, шифрование буквы меняется 9 раз.
1740198311069.png


Движение роторов.​

На этом основная часть механизма рассмотрена, но есть последняя деталь — повороты роторов. Для начала нужно понять, что роторы в принципе являются движущимся элементом. Перед началом написания шифрованного текста нужно выставить каждый ротор в конкретное положение, от этого будет зависеть, какая буква какую будет заменять. Но в дополнение к этому роторы будут автоматически менять своё положение во время написания текста. Работает это с помощью "звёздочек с зазубренными" у каждого ротора.
1740198340491.png



У каждого ротора есть толкающий элемент, который цепляется за зазубрины и поворачивает ротор. Но у каждого ротора зазубрины выглядели по-разному. Например, на первом роторе были зазубрины, которые с одной стороны мешали соприкосновению толкающего элемента со вторым ротором, пока первый ротор не встанет в определённую позицию.

Когда второй ротор не может сцепиться с движущимся элементом:
1740198390263.png


Это происходит из-за того, что часть первого ротора мешает, но когда первый ротор встаёт в определённое положение, в котором у него есть выемка, благодаря которой толкающий элемент второго ротора проходит свободно, второй ротор также начинает движение.
1740198418694.png


1740198440020.png


На этом разбор основного механизма завершён. Именно он и будет служить основой для написания метода шифрования.

Какие механизмы будут в коде?​

Теперь кратко составим список механизмов, которые нужно реализовать в коде:

  1. 4 ротора с алфавитом в произвольном порядке (симуляция запутанных проводов двух контактов, как в оригинальной машине).
  2. Рефлектор также с перемешанным алфавитом.
  3. Изначальные позиции роторов при старте (какие контакты роторов будут соединены с контактами других роторов).
  4. Указание позиции ротора, при которой он будет смещаться (симуляция поворота ротора).

Тестовая программа.​

Теперь приступим к написанию кода. Для начала будет показана тестовая программа с использованием шифрования, затем программа для расшифровки. После этого код для шифрования и расшифровки будет адаптирован под самописный холодный кошелёк из предыдущей статьи.

Система роторов.​

Для начала рассмотрим систему роторов, которая представлена в виде списка, в котором находится 4 элемента. Эти элементы представляют собой кортежи, состоящие из проводки и метки переключения ротора.

  1. Проводка — это 26 позиций алфавита, но не по порядку, а произвольно перемешанные (симуляция перепутанных соединений контактов внутри ротора).
  2. Метка переключения — это симуляция того самого толкающего элемента и зазубрины, за которую он цепляется на роторе, чтобы повернуть его.

Python: Скопировать в буфер обмена
Код:
rotors = [
   ("EKMFLGDQVZNTOWYHXUSPAIBRCJ", "K"),  # Ротор 1
   ("AJDKSIRUXBLHWTMCQGZNPYFVOE", "J"),  # Ротор 2
   ("BDFHJLCPRTXVZNYEIWGAKMUSQO", "O"),  # Ротор 3
   ("ESOVPZJAYQUIRHXLNFTGKDCMWB", "D")   # Ротор 4
]

Подробнее о метке переключения:​

Начнём с того, что первый ротор двигается постоянно при каждом вводе буквы, двигается он влево. То есть, когда будет введена первая буква, проводка первого ротора будет не "EKMFLGDQVZNTOWYHXUSPAIBRCJ", а "KMFLGDQVZNTOWYHXUSPAIBRCJE", и K станет на основную позицию. Раз она встала на основную позицию и в метке указана как раз K, значит второй ротор также смещается в позиции и становится не "AJDKSIRUXBLHWTMCQGZNPYFVOE", а "JDKSIRUXBLHWTMCQGZNPYFVOEA". В следующий раз второй ротор поменяет свою позицию, когда у первого ротора на основной позиции снова будет K, то есть после полного прохождения по кругу. Таким же образом работает и третий ротор.

У четвёртого ротора также есть метка, но это последний ротор, и сдвигать ему нечего, кроме самого себя. Так что получается, что, достигнув метки на роторе 3, двигается четвёртый ротор, а когда на четвёртом роторе достигает указанная в нём метка, он также сдвигается в позиции.


Изначальная позиция роторов:​

От изначальной позиции роторов зависит, когда каждый из роторов достигнет метки переключения. Эти позиции будут использоваться в качестве пароля.
Python: Скопировать в буфер обмена
password = "QWER"

Рефлектор:​

reflector = "YRUHQSLDPXNGOKMIEBFZCWVJAT" # Рефлекторная схема
В реализации в виде кода это по сути как ротор, только у него нет вращения и выставления изначальной позиции. Он служит лишь для ещё одной смены буквы, а затем выбранная буква проходит опять по всем роторам в обратном порядке.

Запуск процесса шифрования:​

Python: Скопировать в буфер обмена
Код:
message = "Good night world"
encrypted = enigma_cipher()
print(f"Результат: {encrypted}")
  1. Сообщение, которое нужно зашифровать
  2. Вызов функции, которая начинает процесс шифрования, и запись результата в переменную.
  3. Вывод результата в консоль.

Функция enigma_cipher:​

Python: Скопировать в буфер обмена
Код:
def enigma_cipher():
   positions = list(password)
   current_positions = positions.copy()

   # Пустой объект для хранения результата
   result = []

   for original_char in message:
       if not original_char.isalpha():
           result.append(original_char)
           continue

       char = original_char.upper()

       # Логика вращения роторов
       rotate_next = True
       for i in range(len(current_positions)):
           if rotate_next:
               new_pos = (string.ascii_uppercase.index(current_positions[i]) + 1) % 26
               current_positions[i] = string.ascii_uppercase[new_pos]
               rotate_next = current_positions[i] == rotors[i][1]
           else:
               break

       # Шифрование символа
       processed = char
       for i in range(len(rotors)):
           wiring = rotors[i][0]
           pos = current_positions[i]
           offset = string.ascii_uppercase.index(pos)
           processed = wiring[(string.ascii_uppercase.index(processed) + offset) % 26]

       # Рефлектор
       processed = reflector[string.ascii_uppercase.index(processed)]

       # Обратный проход
       for i in reversed(range(len(rotors))):
           wiring = rotors[i][0]
           pos = current_positions[i]
           offset = string.ascii_uppercase.index(pos)
           processed = string.ascii_uppercase[(wiring.index(processed) - offset) % 26]

       result.append(processed)

   return ''.join(result)

В данной функции будет происходить абсолютно вся логика шифрования или расшифровки. Рассмотрим эту функцию по частям.

positions = list(password)
Преобразует строку в список. Было "QWER", стало ['Q', 'W', 'E', 'R']. Это нужно, чтобы обращаться к каждой букве отдельно.

current_positions = positions.copy()
Создаёт копию позиций роторов. Это нужно, чтобы не менять оригинальный пароль, так как во время шифрования ротор будет крутиться, и соответственно позиции будут меняться.

result = []
Объект, в котором будут храниться результаты.

Python: Скопировать в буфер обмена
Код:
if not original_char.isalpha():
   result.append(original_char)
   continue
Пропускаем все символы, которые не являются буквами, так как их шифровать не получится, ведь в проводке ротора только алфавит.

char = original_char.upper()
Перевод буквы из сообщения в верхний регистр. Это сделано потому, что в роторах используются только буквы верхнего регистра.

Поворот роторов:​

После подготовки следует первый этап начала шифрования — это поворот роторов.
Python: Скопировать в буфер обмена
Код:
rotate_next = True
for i in range(len(current_positions)):
   if rotate_next:
       new_pos = (string.ascii_uppercase.index(current_positions[i]) + 1) % 26
       current_positions[i] = string.ascii_uppercase[new_pos]
       rotate_next = current_positions[i] == rotors[i][1]
   else:
       break
Изначально rotate_next = True, что означает, что первый ротор всегда прокручивается. Затем в условии if определяется, дошел ли текущий ротор до метки переключения или нет. Если не дошел, то следующий ротор не производит движение.

positions[i] = alphabet[(alphabet.index(positions[i]) + 1) % 26]
Поворачиваем ротор на одну позицию (Берется текущая позиция ротора и к ней добавляется +1).

current_positions[i] = string.ascii_uppercase[new_pos]
Обновляем позицию ротора в current_positions.

rotate_next = current_positions[i] == rotors[i][1]
Проверяем, нужно ли переключить следующий ротор. Если текущая позиция ротора совпадает с меткой переключения (notch), то флаг rotate_next будет установлен в True, и следующий ротор будет сдвигаться.

Таким образом, в цикле происходит проверка всех роторов, и при необходимости их поворот с сохранением новых позиций роторов.

Шифрование:​

Теперь начинается шифрование. Первый его этап — это прохождение буквы через все роторы, от первого до четвертого.
Python: Скопировать в буфер обмена
Код:
for i in range(len(rotors)):
   wiring = rotors[i][0]
   pos = current_positions[i]
   offset = string.ascii_uppercase.index(pos)
   processed = wiring[(string.ascii_uppercase.index(processed) + offset) % 26]

  • wiring — проводка ротора.
  • pos — позиция ротора.
  • offset — определяем, на сколько ротор сдвинут относительно алфавита.
  • processed — прохождение символа через ротор.

Рефлектор:​

Затем следует проход буквы через рефлектор. Рефлектор просто заменяет переданную букву на соответствующую позицию в перепутанном алфавите, указанном в рефлекторе.
processed = reflector[string.ascii_uppercase.index(processed)]

Обратный проход через роторы:​

Это последний этап шифрования. Полученная буква после рефлектора проходит снова по всем роторам в обратном порядке и меняет свое значение.
Python: Скопировать в буфер обмена
Код:
for i in reversed(range(len(rotors))):
   wiring = rotors[i][0]  # Проводка ротора
   pos = current_positions[i]  # Его позиция
   # Определяем, на сколько ротор сдвинут относительно алфавита.
   offset = string.ascii_uppercase.index(pos)
   # Проход символа через ротор
   processed = string.ascii_uppercase[(wiring.index(processed) - offset) % 26]

Результат:​

Запускаем код, и в качестве сообщения будет фраза "Good night world".
1740198795501.png



Если требуется расшифровать слово, то нужно просто в этом же коде указать зашифрованный текст. Текст пройдет через все роторы и вернет исходное сообщение (при условии, что проводка в роторах и пароль (начальные позиции роторов) такие же, как при шифровании).

Адаптация метода для холодного кошелька.​

Теперь попробуем адаптировать данный код шифрования и расшифровки под самописный холодный кошелек, написанный мной в предыдущей статье - Статья - Самописный холодный кошелек Dash | XSS.is (ex DaMaGeLaB)

В самописном холодном кошельке использовалось шифрование AES-GCM. Вся логика была в классе CryptoUtils.
Python: Скопировать в буфер обмена
Код:
class CryptoUtils:
   @staticmethod
   def encrypt(data: str, password: str) -> str:
       salt = get_random_bytes(16)
       key = scrypt(password.encode(), salt, key_len=32, N=2 ** 20, r=8, p=1)
       nonce = get_random_bytes(12)


       cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
       ciphertext, tag = cipher.encrypt_and_digest(data.encode())


       return base64.b64encode(salt + nonce + ciphertext + tag).decode('utf-8')


   @staticmethod
   def decrypt(encrypted_data: str, password: str) -> str:
       encrypted_bytes = base64.b64decode(encrypted_data)


       salt = encrypted_bytes[:16]
       nonce = encrypted_bytes[16:28]
       ciphertext = encrypted_bytes[28:-16]
       tag = encrypted_bytes[-16:]


       key = scrypt(password.encode(), salt, key_len=32, N=2 ** 20, r=8, p=1)
       cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)


       try:
           return cipher.decrypt_and_verify(ciphertext, tag).decode('utf-8')
       except ValueError:
           raise ValueError("Неверный пароль или поврежденные данные")

Именно в этом классе и будет полностью заменена логика шифрования с AES на Энигму.
Python: Скопировать в буфер обмена
Код:
class CryptoUtils:
   @staticmethod
   def encrypt(data: str, password: str) -> str:
       if len(password) != 4 or not all(c.upper() in string.ascii_uppercase for c in password):
           raise ValueError("Пароль должен состоять из 4 букв A-Z")
       encrypted = CryptoUtils.enigma_cipher(data, password.upper())
       return encrypted

   @staticmethod
   def decrypt(encrypted_data: str, password: str) -> str:
       if len(password) != 4 or not all(c.upper() in string.ascii_uppercase for c in password):
           raise ValueError("Пароль должен состоять из 4 букв A-Z")
       decrypted = CryptoUtils.enigma_cipher(encrypted_data, password.upper())
       return decrypted


   @staticmethod
   def enigma_cipher(message: str, password: str) -> str:
       rotors = [
           ("EKMFLGDQVZNTOWYHXUSPAIBRCJ", "K"),
           ("AJDKSIRUXBLHWTMCQGZNPYFVOE", "J"),
           ("BDFHJLCPRTXVZNYEIWGAKMUSQO", "O"),
           ("ESOVPZJAYQUIRHXLNFTGKDCMWB", "D")
       ]
       reflector = "YRUHQSLDPXNGOKMIEBFZCWVJAT"

       password = password.upper()

       positions = list(password[:4])

       result = []
       current_positions = positions.copy()

       for original_char in message:
           if not original_char.isalpha():
               result.append(original_char)
               continue

           is_upper = original_char.isupper()
           char = original_char.upper()

           rotate_next = True
           for i in range(len(current_positions)):
               if rotate_next:
                   new_pos = (string.ascii_uppercase.index(current_positions[i]) + 1) % 26
                   current_positions[i] = string.ascii_uppercase[new_pos]
                   rotate_next = current_positions[i] == rotors[i][1]
               else:
                   break

           # Шифрование символа
           processed = char
           for i in range(len(rotors)):
               wiring = rotors[i][0]
               pos = current_positions[i]
               offset = string.ascii_uppercase.index(pos)
               processed = wiring[(string.ascii_uppercase.index(processed) + offset) % 26]

           processed = reflector[string.ascii_uppercase.index(processed)]

           # Обратный проход
           for i in reversed(range(len(rotors))):
               wiring = rotors[i][0]
               pos = current_positions[i]
               offset = string.ascii_uppercase.index(pos)
               processed = string.ascii_uppercase[(wiring.index(processed) - offset) % 26]

           # Восстанавливаем регистр
           result.append(processed if is_upper else processed.lower())

       return ''.join(result)

Вся основная логика метода для шифрования и расшифровки осталась абсолютно такой же, как и в тестовой версии шифрования, показанной ранее. Также в тестовой версии всё шифровалось и расшифровывалось в верхнем регистре. Теперь это не так, так как адреса и приватные ключи криптовалютных кошельков чувствительны к регистру.

Как это работает сейчас?​

В роторах всё так же имеются буквы только верхнего регистра, но перед шифрованием запоминается оригинальный регистр каждого знака, и после шифрования полученная новая буква переводится в регистр оригинала. Точно так же происходит и при расшифровке.

Реализация сохранения оригинального регистра:​

is_upper = original_char.isupper()
Если изначально символ в верхнем регистре, то значение true, если в нижнем — false. Результат записывается в переменную. Это нужно для того, чтобы запомнить, в каком регистре была буква изначально.

char = original_char.upper()
Перевод символа в верхний регистр в любом случае.

В самом конце, после шифрования восстанавливается изначальный регистр.
result.append(processed if is_upper else processed.lower())
Если is_upper == True, то буква остается в верхнем регистре, если False, то переводится в нижний регистр.

Как происходит работа с паролем?​

Пароль при создании кошелька можно указать в любом регистре, т.к. при работе с шифрованием он будет переведен в любом случае в верхний регистр.
password = password.upper()

Затем пароль обрезается до 4 символов, если пользователь при создании кошелька ввел больше четырех.
positions = list(password[:4])

Как выглядит зашифрованный файл кошелька:​

1740199005083.png



Как выглядит в расшифрованном виде в веб интерфейсе кошелька:​

1740199027816.png



Использование метода в чате с отправкой зашифрованных сообщений.​

С внедрением метода шифрования в холодный кошелек закончено, и теперь рассмотрим внедрение в простой чат с отправкой сообщений по сокетам.

Как устроен сервер?​

Для начала будет написан сервер, который будет ожидать подключение от двух клиентов. После подключения обоих клиентов будут создаваться два потока: один поток будет отправлять сообщения от первого клиента второму, а второй поток будет принимать сообщения от второго клиента и передавать первому.

Для начала нужно указать адрес и порт сервера и вызвать основную функцию работы сервера:
Python: Скопировать в буфер обмена
Код:
if __name__ == "__main__":
   HOST = 'localhost'
   PORT = 12345
   main()

В основной функции первым делом нужно создать сокет и привязать его к адресу сервера.
Python: Скопировать в буфер обмена
Код:
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))

Затем нужно прослушивать подключения к серверу и добавить проверку на то, что подключилось уже 2 пользователя.
Python: Скопировать в буфер обмена
Код:
server.listen(2)
print("Сервер запущен. Ожидание двух подключений")

clients = []

while len(clients) < 2:
   client, addr = server.accept()
   print(f"Клиент подключен")
   clients.append(client)
  1. server.listen(2) — слушаем подключения.
  2. clients — список, в котором будут храниться подключенные пользователи.
  3. while len(clients) < 2 — будет работать, пока в списке клиентов не будет 2 клиента.

client, addr = server.accept()
Когда клиент подключается, он добавляется в переменную client, а в addr добавляется его адрес.

clients.append(client)
Добавление клиента из переменной в список clients.

Когда цикл закончится, новые клиенты не будут добавляться в список. После этого для каждого из клиентов нужно создать поток, вызывающий функцию handle_client.
Python: Скопировать в буфер обмена
Код:
client1, client2 = clients
print("Оба клиента подключены")

# Запуск двух потоков, для каждого клиента по потоку
Python: Скопировать в буфер обмена
Код:
threading.Thread(target=handle_client, args=(client1, client2)).start()
threading.Thread(target=handle_client, args=(client2, client1)).start()
В потоках указываются переменные, обозначающие, от какого клиента будут идти сообщения и к какому.

Функция handle_client:​

Python: Скопировать в буфер обмена
Код:
def handle_client(client, other_socket):
   while True:
       try:
           data = client.recv(1024)
           if not data:
               break
           other_socket.send(data)
       except (ConnectionResetError, BrokenPipeError):
           break
   client.close()
В данной функции в бесконечном цикле проверяются отправленные клиентом данные. Затем эти данные отправляются второму клиенту.

data = client.recv(1024)
Этот метод используется для получения данных от клиента. 1024 — это размер ожидаемых данных, можете его увеличить для отправки более крупных сообщений.

other_socket.send(data)
Это метод для отправки данных конкретному клиенту.

Клиентская часть:​

На этом сервер для обмена сообщениями закончен, и можно приступать к клиентской части.
Метод и код для шифрования сообщений остаются абсолютно такими же, как и прежде (версия, в которой сохраняется оригинальный регистр букв).
Python: Скопировать в буфер обмена
Код:
def enigma_cipher(message: str, password: str) -> str:
   rotors = [
       ("EKMFLGDQVZNTOWYHXUSPAIBRCJ", "K"),
       ("AJDKSIRUXBLHWTMCQGZNPYFVOE", "J"),
       ("BDFHJLCPRTXVZNYEIWGAKMUSQO", "O"),
       ("ESOVPZJAYQUIRHXLNFTGKDCMWB", "D")
   ]
   reflector = "YRUHQSLDPXNGOKMIEBFZCWVJAT"
   password = password.upper().ljust(4, 'A')[:4]
   positions = list(password)
   current_positions = positions.copy()
   result = []

   for original_char in message:
       if not original_char.isalpha():
           result.append(original_char)
           continue

       is_upper = original_char.isupper()
       char = original_char.upper()

       rotate_next = True
       for i in range(len(current_positions)):
           if rotate_next:
               new_pos = (string.ascii_uppercase.index(current_positions[i]) + 1) % 26
               current_positions[i] = string.ascii_uppercase[new_pos]
               rotate_next = current_positions[i] == rotors[i][1]
           else:
               break

       processed = char
       for i in range(len(rotors)):
           wiring = rotors[i][0]
           pos = current_positions[i]
           offset = string.ascii_uppercase.index(pos)
           processed = wiring[(string.ascii_uppercase.index(processed) + offset) % 26]

       processed = reflector[string.ascii_uppercase.index(processed)]

       for i in reversed(range(len(rotors))):
           wiring = rotors[i][0]
           pos = current_positions[i]
           offset = string.ascii_uppercase.index(pos)
           processed = string.ascii_uppercase[(wiring.index(processed) - offset) % 26]

       result.append(processed if is_upper else processed.lower())

   return ''.join(result)

Основная функция работы клиента:​

Python: Скопировать в буфер обмена
Код:
def main():
   HOST = 'localhost'
   PORT = 12345
   password = input("Введите пароль для шифрования (мин. 4 символа): ").strip()

   while len(password) < 4:
       print("Пароль должен содержать только 4 буквы")
       password = input("Введите пароль для шифрования: ").strip()

   client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   try:
       client_socket.connect((HOST, PORT))
       print("Подключено к серверу!")
   except ConnectionRefusedError:
       print("Сервер недоступен!")
       return

   receiver = threading.Thread(target=receive_messages, args=(client_socket, password))
   receiver.daemon = True
   receiver.start()

   try:
       send_messages(client_socket, password)
   except KeyboardInterrupt:
       pass
   finally:
       client_socket.close()

Python: Скопировать в буфер обмена
Код:
HOST = 'localhost'
   PORT = 12345
   password = input("Введите пароль для шифрования (мин. 4 символа): ").strip()
Указание адреса и порта сервера. input означает, что в консоль можно будет ввести значение, которое запишется в password..

Python: Скопировать в буфер обмена
Код:
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
   client_socket.connect((HOST, PORT))
   print("Подключено к серверу!")
except ConnectionRefusedError:
   print("Сервер недоступен!")
   return
Создаём сокет и подключаемся к серверу по указанному ранее адресу.

Python: Скопировать в буфер обмена
Код:
receiver = threading.Thread(target=receive_messages, args=(client_socket, password))
receiver.start()
Создаём отдельный поток для обработки полученных сообщений от сервера (изначально отправленных другим клиентом).

Вызов функции для отправки сообщений.
send_messages(client_socket, password)

Функция для обработки полученных сообщений:​

Python: Скопировать в буфер обмена
Код:
def receive_messages(sock, password):
   while True:
       try:
           # Получаем данные от клиента
           data = sock.recv(1024).decode('utf-8')
           # Расшифровываем сообщение
           decrypted = enigma_cipher(data, password)
           print(f"\n[Другой] Зашифрованное: {data}")
           print(f"[Другой] Расшифрованное: {decrypted}")
           print("> ", end="", flush=True)  # Для сохранения возможности ввода
       except (ConnectionResetError, KeyboardInterrupt):
           print("\nСоединение закрыто")
           break
Функция достаточно простая: в бесконечном цикле получаем данные от сервера, вызываем функцию для расшифровки, результат расшифровки выводим в консоль.

Функция отправки сообщений:​

Python: Скопировать в буфер обмена
Код:
def send_messages(sock, password):
   while True:
       try:
           print("> ", end="", flush=True)
           message = input()
           # Шифруем сообщение
           encrypted = enigma_cipher(message, password)
           print(f"\n[Вы] Оригинал: {message}")
           print(f"[Вы] Зашифрованное: {encrypted}")
           sock.send(encrypted.encode('utf-8'))  # Отправляем сообщение
       except (ConnectionResetError, KeyboardInterrupt):
           print("\nСоединение закрыто")
           break

Результат работы чата:​

1740199304792.png



Вывод:​

В данной статье был показан метод шифрования, основанный на «Энигме». Также этот метод был применён в двух программах. В завершение статьи хочу рассказать о плюсах и минусах данного метода шифрования, которые заметил лично я.

Минусы:​

Если у злоумышленника будет точная копия программы с проводкой для всех роторов, то с помощью перебора 4-значного пароля шифр можно будет снять очень просто и быстро.

Плюсы:​

Лично мне кажется, что если злоумышленник получит только шифрованные данные, то даже при условии, что он знает, каким образом данные зашифрованы, он не сможет снять шифр без знания не только позиций роторов, но и проводки каждого ротора (перепутанного алфавита).

Также к плюсам можно отнести скорость шифрования и расшифровки. Расшифровка и шифровка данных в кошельке стали проходить заметно быстрее, чем с AES.


Программы из статьи на GitHub:​

Холодный кошелек Dash - https://github.com/overlordgamedev/Cold-Wallet-HooliWallet
Чат с отправкой шифрованных сообщений через сокеты - https://github.com/overlordgamedev/Enigma-Secure-Chat/tree/main

Статья в виде документа - https://docs.google.com/document/d/...CVnST7Ddw/edit?tab=t.0#heading=h.5vnuuj1igkv1


Сделано OverlordGameDev специально для форума XSS.IS
 
Сверху Снизу