Skip to content

Commit

Permalink
v1.0, raw
Browse files Browse the repository at this point in the history
  • Loading branch information
DingJunyao committed Dec 6, 2021
1 parent 1e997a8 commit 0bd56b3
Show file tree
Hide file tree
Showing 25 changed files with 734 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/enigma.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

122 changes: 122 additions & 0 deletions README.md
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.
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[build-system]
requires = [
"setuptools",
"wheel"
]
build-backend = "setuptools.build_meta"
28 changes: 28 additions & 0 deletions setup.cfg
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
Empty file added src/__init__.py
Empty file.
1 change: 1 addition & 0 deletions src/enigma/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .enigma import Enigma
100 changes: 100 additions & 0 deletions src/enigma/enigma.py
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)
1 change: 1 addition & 0 deletions src/enigma/part/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .plugboard import Plugboard
3 changes: 3 additions & 0 deletions src/enigma/part/plate/__init__.py
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
13 changes: 13 additions & 0 deletions src/enigma/part/plate/entry_plate.py
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)
Loading

0 comments on commit 0bd56b3

Please sign in to comment.