-
-
Notifications
You must be signed in to change notification settings - Fork 46.8k
Added enigma machine emulator #2345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
62997ca
f72ab63
3ef11fc
de957d7
2bbdbaa
045e477
ee75354
6828f5f
baae09b
e5fc5d5
7308a85
a98f1ea
0dbef08
d09dfc7
fa137fd
3d60821
74cf54c
fecaefc
c2553be
8dd7221
89a00b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,254 @@ | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
Wikipedia: https://en.wikipedia.org/wiki/Enigma_machine | ||||||||||||||||||||||||||||
Video explanation: https://youtu.be/QwQVMqfoB2E | ||||||||||||||||||||||||||||
Also check out Numberphile's and Computerphile's videos on this topic | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
This module contains function 'enigma' which emulates | ||||||||||||||||||||||||||||
the famous Enigma machine from WWII. | ||||||||||||||||||||||||||||
Module includes: | ||||||||||||||||||||||||||||
- enigma function | ||||||||||||||||||||||||||||
- showcase of function usage | ||||||||||||||||||||||||||||
- 9 randnomly generated rotors | ||||||||||||||||||||||||||||
- reflector (aka static rotor) | ||||||||||||||||||||||||||||
- original alphabet | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
Created by TrapinchO | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# used alphabet -------------------------- | ||||||||||||||||||||||||||||
# from string.ascii_uppercase | ||||||||||||||||||||||||||||
abc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# -------------------------- default selection -------------------------- | ||||||||||||||||||||||||||||
# rotors -------------------------- | ||||||||||||||||||||||||||||
rotor1 = 'EGZWVONAHDCLFQMSIPJBYUKXTR' | ||||||||||||||||||||||||||||
rotor2 = 'FOBHMDKEXQNRAULPGSJVTYICZW' | ||||||||||||||||||||||||||||
rotor3 = 'ZJXESIUQLHAVRMDOYGTNFWPBKC' | ||||||||||||||||||||||||||||
# reflector -------------------------- | ||||||||||||||||||||||||||||
reflector = {'A': 'N', 'N': 'A', 'B': 'O', 'O': 'B', 'C': 'P', 'P': 'C', 'D': 'Q', | ||||||||||||||||||||||||||||
'Q': 'D', 'E': 'R', 'R': 'E', 'F': 'S', 'S': 'F', 'G': 'T', 'T': 'G', | ||||||||||||||||||||||||||||
'H': 'U', 'U': 'H', 'I': 'V', 'V': 'I', 'J': 'W', 'W': 'J', 'K': 'X', | ||||||||||||||||||||||||||||
'X': 'K', 'L': 'Y', 'Y': 'L', 'M': 'Z', 'Z': 'M'} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# -------------------------- extra rotors -------------------------- | ||||||||||||||||||||||||||||
rotor4 = 'RMDJXFUWGISLHVTCQNKYPBEZOA' | ||||||||||||||||||||||||||||
rotor5 = 'SGLCPQWZHKXAREONTFBVIYJUDM' | ||||||||||||||||||||||||||||
rotor6 = 'HVSICLTYKQUBXDWAJZOMFGPREN' | ||||||||||||||||||||||||||||
rotor7 = 'RZWQHFMVDBKICJLNTUXAGYPSOE' | ||||||||||||||||||||||||||||
rotor8 = 'LFKIJODBEGAMQPXVUHYSTCZRWN' | ||||||||||||||||||||||||||||
rotor9 = 'KOAEGVDHXPQZMLFTYWJNBRCIUS' | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def _validator(rotpos: tuple, rotsel: tuple, pb: str) -> tuple: | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
Checks if the values can be used for the 'enigma' function | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
>>> _validator((1,1,1), (rotor1, rotor2, rotor3), 'POLAND') | ||||||||||||||||||||||||||||
((1, 1, 1), ('EGZWVONAHDCLFQMSIPJBYUKXTR', 'FOBHMDKEXQNRAULPGSJVTYICZW', \ | ||||||||||||||||||||||||||||
'ZJXESIUQLHAVRMDOYGTNFWPBKC'), \ | ||||||||||||||||||||||||||||
{'P': 'O', 'O': 'P', 'L': 'A', 'A': 'L', 'N': 'D', 'D': 'N'}) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
:param rotpos: rotor_positon | ||||||||||||||||||||||||||||
:param rotsel: rotor_selection | ||||||||||||||||||||||||||||
:param pb: plugb -> validated and transformed | ||||||||||||||||||||||||||||
:return: (rotpos, rotsel, pb) | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
# Checks if there are 3 unique rotors | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
unique_rotsel = len(set(rotsel)) | ||||||||||||||||||||||||||||
if unique_rotsel < 3: | ||||||||||||||||||||||||||||
raise Exception(f'Please use 3 unique rotors (not {unique_rotsel})') | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# Checks if rotor positions are valid | ||||||||||||||||||||||||||||
rotorpos1, rotorpos2, rotorpos3 = rotpos | ||||||||||||||||||||||||||||
if not 0 < rotorpos1 <= len(abc): | ||||||||||||||||||||||||||||
raise ValueError(f'First rotor position is not within range of 1..26 (' | ||||||||||||||||||||||||||||
f'{rotorpos1}') | ||||||||||||||||||||||||||||
if not 0 < rotorpos2 <= len(abc): | ||||||||||||||||||||||||||||
raise ValueError(f'Second rotor position is not within range of 1..26 (' | ||||||||||||||||||||||||||||
f'{rotorpos2})') | ||||||||||||||||||||||||||||
if not 0 < rotorpos3 <= len(abc): | ||||||||||||||||||||||||||||
raise ValueError(f'Third rotor position is not within range of 1..26 (' | ||||||||||||||||||||||||||||
f'{rotorpos3})') | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# Validates string and returns dict | ||||||||||||||||||||||||||||
pb = _plugboard(pb) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
return rotpos, rotsel, pb | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def _plugboard(pbl: str) -> dict: | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
https://en.wikipedia.org/wiki/Enigma_machine#Plugboard | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
>>> _plugboard('PICTURES') | ||||||||||||||||||||||||||||
{'P': 'I', 'I': 'P', 'C': 'T', 'T': 'C', 'U': 'R', 'R': 'U', 'E': 'S', 'S': 'E'} | ||||||||||||||||||||||||||||
>>> _plugboard('POLAND') | ||||||||||||||||||||||||||||
{'P': 'O', 'O': 'P', 'L': 'A', 'A': 'L', 'N': 'D', 'D': 'N'} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
Pairs can be separated by spaces | ||||||||||||||||||||||||||||
:param pbl: string containing plugboard setting for the Enigma machine | ||||||||||||||||||||||||||||
:return: dictionary of | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dictionary of ??? |
||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# tests the input string if it | ||||||||||||||||||||||||||||
# a) is type string | ||||||||||||||||||||||||||||
# b) has even length (so pairs can be made) | ||||||||||||||||||||||||||||
if not isinstance(pbl, str): | ||||||||||||||||||||||||||||
raise TypeError(f'Plugboard setting isn\'t type string ({type(pbl)})') | ||||||||||||||||||||||||||||
elif len(pbl) % 2 != 0: | ||||||||||||||||||||||||||||
raise Exception(f'Odd number of symbols ({len(pbl)})') | ||||||||||||||||||||||||||||
elif pbl == '': | ||||||||||||||||||||||||||||
return {} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
pbl.replace(' ', '') | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# Checks if all characters are unique | ||||||||||||||||||||||||||||
tmppbl = set() | ||||||||||||||||||||||||||||
for i in pbl: | ||||||||||||||||||||||||||||
if i not in abc: | ||||||||||||||||||||||||||||
raise Exception('Not in list of symbols') | ||||||||||||||||||||||||||||
elif i in tmppbl: | ||||||||||||||||||||||||||||
raise Exception(f'Duplicate symbol ({i})') | ||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||
tmppbl.add(i) | ||||||||||||||||||||||||||||
del tmppbl | ||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would you tell the user which symbol is invalid/duplicate? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bad_symbols = [key for key, value in collections.Counter(pbl) if key not in abc or value > 1]
if bad_symbols:
raise ValueError(<your code here>) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think my version is easier to understand |
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# Created the dictionary | ||||||||||||||||||||||||||||
pb = {} | ||||||||||||||||||||||||||||
for i in range(0, len(pbl) - 1, 2): | ||||||||||||||||||||||||||||
pb[pbl[i]] = pbl[i + 1] | ||||||||||||||||||||||||||||
pb[pbl[i + 1]] = pbl[i] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
return pb | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
def enigma(text: str, rotor_position: tuple, | ||||||||||||||||||||||||||||
rotor_selection: tuple = (rotor1, rotor2, rotor3), plugb: str = '') -> str: | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
The only difference with real-world enigma is that I allowed string input. | ||||||||||||||||||||||||||||
All characters are converted to uppercase. (non-letter symbol are ignored) | ||||||||||||||||||||||||||||
How it works: | ||||||||||||||||||||||||||||
(for every letter in the message) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
- Input letter goes into the plugboard. | ||||||||||||||||||||||||||||
If it is connected to another one, switch it. | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
- Letter goes through 3 rotors. | ||||||||||||||||||||||||||||
Each rotor can be represented as 2 sets of symbol, where one is shuffled. | ||||||||||||||||||||||||||||
Each symbol from the first set has corresponding symbol in | ||||||||||||||||||||||||||||
the second set and vice versa. | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
example: | ||||||||||||||||||||||||||||
| ABCDEFGHIJKLMNOPQRSTUVWXYZ | e.g. F=D and D=F | ||||||||||||||||||||||||||||
| VKLEPDBGRNWTFCJOHQAMUZYIXS | | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
- Symbol then goes through reflector (static rotor). | ||||||||||||||||||||||||||||
There it is switched with paired symbol | ||||||||||||||||||||||||||||
The reflector can be represented as2 sets, each with half of the alphanet. | ||||||||||||||||||||||||||||
There are usually 10 pairs of letters. | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
Example: | ||||||||||||||||||||||||||||
| ABCDEFGHIJKLM | e.g. E is paired to X | ||||||||||||||||||||||||||||
| ZYXWVUTSRQPON | so when E goes in X goes out and vice versa | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
- Letter then goes through the rotors again | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
- If the letter is connected to plugboard, it is switched. | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
- Return the letter | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
>>> enigma('Hello World!', (1, 2, 1), plugb='pictures') | ||||||||||||||||||||||||||||
'KORYH JUHHI!' | ||||||||||||||||||||||||||||
>>> enigma('KORYH, juhhi!', (1, 2, 1), plugb='pictures') | ||||||||||||||||||||||||||||
'HELLO, WORLD!' | ||||||||||||||||||||||||||||
>>> enigma('hello world!', (1, 1, 1), plugb='pictures') | ||||||||||||||||||||||||||||
'FPNCZ QWOBU!' | ||||||||||||||||||||||||||||
>>> enigma('FPNCZ QWOBU', (1, 1, 1), plugb='pictures') | ||||||||||||||||||||||||||||
'HELLO WORLD' | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
:param text: input message | ||||||||||||||||||||||||||||
:param rotor_position: tuple with 3 values in range 1..26 | ||||||||||||||||||||||||||||
:param rotor_selection: tuple with 3 rotors () | ||||||||||||||||||||||||||||
:param plugb: string containing plugboard configuration (default '') | ||||||||||||||||||||||||||||
:return: en/decrypted string | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
text = text.upper() | ||||||||||||||||||||||||||||
rotor_position, rotor_selection, pb = _validator( | ||||||||||||||||||||||||||||
rotor_position, rotor_selection, plugb.upper()) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
rotorpos1, rotorpos2, rotorpos3 = rotor_position | ||||||||||||||||||||||||||||
rotor1, rotor2, rotor3 = rotor_selection | ||||||||||||||||||||||||||||
rotorpos1 -= 1 | ||||||||||||||||||||||||||||
rotorpos2 -= 1 | ||||||||||||||||||||||||||||
rotorpos3 -= 1 | ||||||||||||||||||||||||||||
pb = pb | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
result = [] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# encryption/decryption process -------------------------- | ||||||||||||||||||||||||||||
for symbol in text: | ||||||||||||||||||||||||||||
if symbol in abc: | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# 1st plugboard -------------------------- | ||||||||||||||||||||||||||||
if symbol in pb: | ||||||||||||||||||||||||||||
symbol = pb[symbol] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# rotor ra -------------------------- | ||||||||||||||||||||||||||||
index = abc.index(symbol) + rotorpos1 | ||||||||||||||||||||||||||||
symbol = rotor1[index % len(abc)] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# rotor rb -------------------------- | ||||||||||||||||||||||||||||
index = abc.index(symbol) + rotorpos2 | ||||||||||||||||||||||||||||
symbol = rotor2[index % len(abc)] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# rotor rc -------------------------- | ||||||||||||||||||||||||||||
index = abc.index(symbol) + rotorpos3 | ||||||||||||||||||||||||||||
symbol = rotor3[index % len(abc)] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# reflector -------------------------- | ||||||||||||||||||||||||||||
# this is the reason you don't need another machine to decipher | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
symbol = reflector[symbol] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# 2nd rotors | ||||||||||||||||||||||||||||
symbol = abc[rotor3.index(symbol) - rotorpos3] | ||||||||||||||||||||||||||||
symbol = abc[rotor2.index(symbol) - rotorpos2] | ||||||||||||||||||||||||||||
symbol = abc[rotor1.index(symbol) - rotorpos1] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# 2nd plugboard | ||||||||||||||||||||||||||||
if symbol in pb: | ||||||||||||||||||||||||||||
symbol = pb[symbol] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# moves/resets rotor positions | ||||||||||||||||||||||||||||
rotorpos1 += 1 | ||||||||||||||||||||||||||||
if rotorpos1 >= len(abc): | ||||||||||||||||||||||||||||
rotorpos1 = 0 | ||||||||||||||||||||||||||||
rotorpos2 += 1 | ||||||||||||||||||||||||||||
if rotorpos2 >= len(abc): | ||||||||||||||||||||||||||||
rotorpos2 = 0 | ||||||||||||||||||||||||||||
rotorpos3 += 1 | ||||||||||||||||||||||||||||
if rotorpos3 >= len(abc): | ||||||||||||||||||||||||||||
rotorpos3 = 0 | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# else: | ||||||||||||||||||||||||||||
# pass | ||||||||||||||||||||||||||||
# Error could be also raised | ||||||||||||||||||||||||||||
# raise ValueError( | ||||||||||||||||||||||||||||
# 'Invalid symbol('+repr(symbol)+')') | ||||||||||||||||||||||||||||
result.append(symbol) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
return "".join(result) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
if __name__ == '__main__': | ||||||||||||||||||||||||||||
message = 'This is my Python script that emulates the Enigma machine from WWII.' | ||||||||||||||||||||||||||||
rotor_pos = (1, 1, 1) | ||||||||||||||||||||||||||||
pb = 'pictures' | ||||||||||||||||||||||||||||
rotor_sel = (rotor2, rotor4, rotor8) | ||||||||||||||||||||||||||||
en = enigma(message, rotor_pos, rotor_sel, pb) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
print('Encrypted message:', en) | ||||||||||||||||||||||||||||
print('Decrypted message:', enigma(en, rotor_pos, rotor_sel, pb)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pbl
is a cyptic variable name? Why force your reader to guess what it means? Why not use a more self-documenting variable name likepegboard
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thats even more cryptic (at least for me)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is
pbstring
better?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or inputpb
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With all these names, the reader will always ask... What does pb stand for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could add it to documentation