-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
- Loading branch information
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,124 @@ | ||
# enigma | ||
Python-based Enigma. | ||
|
||
## Install | ||
|
||
```bash | ||
pip install enigma | ||
``` | ||
|
||
## Usage | ||
|
||
### Import | ||
|
||
```python | ||
from enigma import Enigma | ||
``` | ||
|
||
The package also contains some sample plate: | ||
|
||
```python | ||
from enigma.sample_plate.rotor import rotor_I, rotor_II, rotor_III | ||
from enigma.sample_plate.reflector import reflector_B | ||
``` | ||
### Defining | ||
|
||
Make sure the rotors are from right to left: | ||
|
||
```python | ||
e = Enigma([rotor_III(), rotor_II(), rotor_I()], reflector_B()) | ||
``` | ||
|
||
### Encryption | ||
|
||
Just input: | ||
|
||
```python | ||
# Encryption | ||
assert e.input('HELLOWORLDBYTHEAUTHOROFTHISPACKAGEDINGJUNYAO') == 'ILBDAAMTAZIJNXSCSIJJPDDWZBRCCUPGQXGRJXQOFGHL' | ||
``` | ||
|
||
And change the positions of the rotors (Defaults to all origin). | ||
|
||
```python | ||
e.set_position() | ||
assert e.input('ILBDAAMTAZIJNXSCSIJJPDDWZBRCCUPGQXGRJXQOFGHL') == 'HELLOWORLDBYTHEAUTHOROFTHISPACKAGEDINGJUNYAO' | ||
``` | ||
|
||
Number starts from 0, and also from right to left. You can also use letter. | ||
|
||
```python | ||
e.set_position(3, 12, 21) | ||
assert e.input('HELLOWORLDBYTHEAUTHOROFTHISPACKAGEDINGJUNYAO') == 'XTGHAGDIVUPGBZVQSFMBSGLKVQHQWESYRTSRMOOFGRLE' | ||
e.set_position('D', 12, 21) | ||
assert e.input('XTGHAGDIVUPGBZVQSFMBSGLKVQHQWESYRTSRMOOFGRLE') == 'HELLOWORLDBYTHEAUTHOROFTHISPACKAGEDINGJUNYAO' | ||
``` | ||
|
||
### Customize | ||
|
||
You can freely customize your Enigma. For example, customize the circuits of rotors: | ||
|
||
```python | ||
from enigma.part.plate import Rotor, Reflector | ||
|
||
rotor_I = Rotor('EKMFLGDQVZNTOWYHXUSPAIBRCJ', name='Rotor I', turnover='Q') | ||
rotor_II = Rotor('AJDKSIRUXBLHWTMCQGZNPYFVOE', name='Rotor II', turnover='E') | ||
rotor_III = Rotor('BDFHJLCPRTXVZNYEIWGAKMUSQO', name='Rotor III', turnover='V') | ||
reflector_B = Reflector('YRUHQSLDPXNGOKMIEBFZCWVJAT', name='Reflector B') | ||
e = Enigma([rotor_III(), rotor_II(), rotor_I()], reflector_B()) | ||
# same as e above | ||
``` | ||
|
||
A tiny Enigma: | ||
|
||
```python | ||
map_source = 'ASDF' | ||
e_custom_1 = Enigma( | ||
[ | ||
Rotor('AFSD', name='I', map_source=map_source), | ||
Rotor('SDAF', name='II', map_source=map_source), | ||
Rotor('DFAS', name='III', map_source=map_source), | ||
], | ||
Reflector('DFAS', name='R', map_source=map_source), | ||
rotate_up=True, rotate_after_type=True | ||
) | ||
assert e_custom_1.input('AA') == 'SD' | ||
e_custom_1.set_position() | ||
assert e_custom_1.input('SD') == 'AA' | ||
``` | ||
|
||
And even in other character! | ||
|
||
```python | ||
map_source_2 = '甲乙丙丁' | ||
e_custom_2 = Enigma( | ||
[ | ||
Rotor('甲丁乙丙', name='I', map_source=map_source_2), | ||
Rotor('乙丙甲丁', name='II', map_source=map_source_2), | ||
Rotor('丙丁甲乙', name='III', map_source=map_source_2), | ||
], | ||
Reflector('丙丁甲乙', name='R', map_source=map_source_2), | ||
rotate_up=True, rotate_after_type=True | ||
) | ||
assert e_custom_2.input('甲甲') == '乙丙' | ||
e_custom_2.set_position() | ||
assert e_custom_2.input('乙丙') == '甲甲' | ||
``` | ||
|
||
### Plugboard | ||
|
||
Plugboard is also supported. | ||
|
||
```python | ||
e.plugboard.plug('L', 'M') | ||
e.plugboard.plug('O', 'P') | ||
assert e.input('HELLOWORLDBYTHEAUTHOROFTHISPACKAGEDINGJUNYAO') == 'IMKPJAITPZIJNXSCSIJEOEDWZBRMCUOGQXGRJXQPFGHF' | ||
e.set_position() | ||
assert e.input('IMKPJAITPZIJNXSCSIJEOEDWZBRMCUOGQXGRJXQPFGHF') == 'HELLOWORLDBYTHEAUTHOROFTHISPACKAGEDINGJUNYAO' | ||
e.unplug('L') | ||
e.unplug('P') | ||
``` | ||
|
||
## License | ||
|
||
MIT License. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[build-system] | ||
requires = [ | ||
"setuptools", | ||
"wheel" | ||
] | ||
build-backend = "setuptools.build_meta" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
[metadata] | ||
name = enigma | ||
version = 1.0.0 | ||
author = DingJunyao | ||
author_email = [email protected] | ||
description = Python-based Enigma. | ||
long_description = file: README.md | ||
long_description_content_type = text/markdown | ||
url = https://github.com/DingJunyao/enigma | ||
project_urls = | ||
Source = https://github.com/DingJunyao/enigma | ||
Bug Tracker = https://github.com/DingJunyao/enigma/issues | ||
classifiers = | ||
Programming Language :: Python :: 3 | ||
License :: OSI Approved :: MIT License | ||
Operating System :: OS Independent | ||
Topic :: Security :: Cryptography | ||
Topic :: Sociology :: History | ||
Topic :: Education | ||
Topic :: Utilities | ||
[options] | ||
package_dir = | ||
= src | ||
packages = find: | ||
python_requires = >=3.6 | ||
|
||
[options.packages.find] | ||
where = src |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .enigma import Enigma |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
from typing import Union | ||
|
||
from src.enigma.part import Plugboard | ||
from src.enigma.part.plate import Rotor, EntryPlate, Reflector | ||
|
||
|
||
class Enigma: | ||
"""An Enigma machine.""" | ||
|
||
def __init__( | ||
self, rotor_list: Union[list[Rotor], tuple[Rotor]], reflector: Reflector, rotate_up: bool = False, | ||
rotate_after_type: bool = False | ||
): | ||
"""Define the Enigma. | ||
:param rotor_list: The list of the rotors from right to left. Tuple with rotors is also supported. | ||
:param reflector: The reflector of the Enigma. | ||
:param rotate_up: Rotate direction of the rotors. | ||
If it's True, it will rotate from small to large by index. | ||
(If upper is A, lower is B, the plate will rotate from lower to upper when change from A to B.) | ||
Or it's False (Default). | ||
(If upper is B, lower is A, the plate will rotate from upper to lower when change from A to B.) | ||
:param rotate_after_type: Set if the rotors rotate after type. Defaults to False. | ||
:raise AttributeError: If the map_source of any rotor is not equal to that in reflector. | ||
""" | ||
self.rotors = list(rotor_list) | ||
self.reflector = reflector | ||
self.reflector.right_plate = self.rotors[-1] | ||
self.entry_plate = EntryPlate(map_table=self.reflector.map_source, left_plate=self.rotors[0]) | ||
for rotor_index, rotor in enumerate(self.rotors): | ||
if rotor.map_source != self.reflector.map_source: | ||
raise AttributeError(f'map_source of rotor[{rotor_index}] is not equal to that in reflector') | ||
if rotor_index < len(self.rotors) - 1: | ||
rotor.left_plate = self.rotors[rotor_index + 1] | ||
else: | ||
rotor.left_plate = self.reflector | ||
if rotor_index > 0: | ||
rotor.right_plate = self.rotors[rotor_index - 1] | ||
else: | ||
rotor.right_plate = self.entry_plate | ||
for rotor in self.rotors: | ||
rotor.rotate_up = rotate_up | ||
self.rotate_after_type = rotate_after_type | ||
self.plugboard = Plugboard(parent=self) | ||
|
||
def input(self, string: str) -> str: | ||
"""Input string to Enigma. | ||
:param string: String. | ||
:return: String of encrypted result. | ||
""" | ||
new_string_list = [] | ||
for letter_index, letter in enumerate(string): | ||
if not self.rotate_after_type: | ||
self.rotors[0].forward() | ||
if letter in self.plugboard.map_dict: | ||
letter = self.plugboard.map_dict[letter] | ||
# print(f'Letter[{letter_index}]: {letter}') | ||
for rotor_index, rotor in enumerate(self.rotors): | ||
# print(rotor_index, 'i', letter) | ||
# print(rotor_index, ' <', ''.join(rotor.current_state['right'])) | ||
# print(rotor_index, '< ', ''.join(rotor.current_state['left'])) | ||
letter = rotor.encrypt(letter, 'right') | ||
# print(rotor_index, 'o', letter) | ||
# print('R', 'i', letter) | ||
# print('R', '< ', ''.join(self.reflector.current_state['left'])) | ||
# print('R', '> ', ''.join(self.reflector.current_state['right'])) | ||
letter = self.reflector.encrypt(letter, 'right') | ||
# print('R', 'o', letter) | ||
for rotor_index, rotor in enumerate(reversed(self.rotors)): | ||
# print(rotor_index, 'i', letter) | ||
# print(rotor_index, '> ', ''.join(rotor.current_state['left'])) | ||
# print(rotor_index, ' >', ''.join(rotor.current_state['right'])) | ||
letter = rotor.encrypt(letter, 'left') | ||
# print(rotor_index, 'o', letter) | ||
# print('I', 'i', letter) | ||
# print('I', '< ', ''.join(self.entry_plate.current_state['left'])) | ||
# print('I', ' >', ''.join(self.entry_plate.current_state['right'])) | ||
letter = self.entry_plate.encrypt(letter, 'left') | ||
# print('I', 'o', letter) | ||
if letter in self.plugboard.map_dict: | ||
letter = self.plugboard.map_dict[letter] | ||
# print(f'Letter[{letter_index}]: {letter}') | ||
new_string_list.append(letter) | ||
if self.rotate_after_type: | ||
self.rotors[0].forward() | ||
return ''.join(new_string_list) | ||
|
||
def set_position(self, *position_list): | ||
"""Set positions of the rotors. | ||
:param position_list: the position of rotors from right to left. | ||
:raise AttributeError: If position list length is not equal to rotors number. | ||
""" | ||
if not position_list: | ||
position_list = (0, ) * len(self.rotors) | ||
elif len(position_list) != len(self.rotors): | ||
raise AttributeError('Position list length is not equal to rotors number') | ||
for rotor, position in zip(self.rotors, position_list): | ||
rotor.set_position(position) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .plugboard import Plugboard |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .entry_plate import EntryPlate | ||
from .rotor import Rotor | ||
from .reflector import Reflector |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from typing import Union | ||
|
||
from .plate import Plate, _ALPHABET | ||
|
||
|
||
class EntryPlate(Plate): | ||
"""Entry Plate of an Enigma.""" | ||
|
||
def __init__( | ||
self, map_table: str = _ALPHABET, init_position: Union[str, int] = 0, left_plate: Plate = None, | ||
name: str = 'Entry Plate' | ||
): | ||
super().__init__(map_table, init_position, False, None, left_plate, name=name, map_source=map_table) |