enigmapython 1.3.2__tar.gz → 2.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. {enigmapython-1.3.2 → enigmapython-2.0.0}/PKG-INFO +25 -3
  2. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Enigma.py +16 -3
  3. enigmapython-2.0.0/enigmapython/EnigmaDEtw_QWERTZ.py +5 -0
  4. enigmapython-2.0.0/enigmapython/EnigmaK.py +13 -0
  5. enigmapython-2.0.0/enigmapython/EnigmaKEtw_QWERTZ.py +5 -0
  6. enigmapython-2.0.0/enigmapython/EnigmaKRotorI.py +18 -0
  7. enigmapython-2.0.0/enigmapython/EnigmaKRotorII.py +18 -0
  8. enigmapython-2.0.0/enigmapython/EnigmaKRotorIII.py +18 -0
  9. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Etw.py +0 -1
  10. enigmapython-2.0.0/enigmapython/EtwQWERTZ.py +7 -0
  11. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Plugboard.py +0 -1
  12. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/ReflectorUKWA.py +0 -1
  13. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/ReflectorUKWB.py +0 -1
  14. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/ReflectorUKWBThin.py +0 -1
  15. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/ReflectorUKWC.py +0 -1
  16. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/ReflectorUKWCThin.py +0 -1
  17. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/ReflectorUKW_EnigmaB_A133.py +0 -1
  18. enigmapython-2.0.0/enigmapython/ReflectorUKW_EnigmaCommercial.py +15 -0
  19. enigmapython-1.3.2/enigmapython/ReflectorNorwayUKW.py → enigmapython-2.0.0/enigmapython/ReflectorUKW_EnigmaINorway.py +2 -3
  20. enigmapython-1.3.2/enigmapython/ReflectorSonderUKW.py → enigmapython-2.0.0/enigmapython/ReflectorUKW_EnigmaISonder.py +2 -3
  21. enigmapython-1.3.2/enigmapython/ReflectorZUKW.py → enigmapython-2.0.0/enigmapython/ReflectorUKW_EnigmaZ.py +1 -1
  22. enigmapython-2.0.0/enigmapython/RotatingReflector.py +5 -0
  23. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Rotor.py +9 -12
  24. enigmapython-2.0.0/enigmapython/Scrambler.py +30 -0
  25. enigmapython-1.3.2/enigmapython/Scrambler.py → enigmapython-2.0.0/enigmapython/Settable.py +19 -37
  26. enigmapython-2.0.0/enigmapython/SettableReflector.py +8 -0
  27. enigmapython-2.0.0/enigmapython/Utils.py +85 -0
  28. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/XRay.py +3 -3
  29. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython.egg-info/PKG-INFO +25 -3
  30. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython.egg-info/SOURCES.txt +12 -5
  31. {enigmapython-1.3.2 → enigmapython-2.0.0}/setup.py +1 -1
  32. enigmapython-1.3.2/enigmapython/EnigmaDEtw_JWULCM.py +0 -6
  33. enigmapython-1.3.2/enigmapython/EnigmaDEtw_QWERTZ.py +0 -6
  34. enigmapython-1.3.2/enigmapython/ReflectorDUKW.py +0 -13
  35. enigmapython-1.3.2/enigmapython/RotatingReflector.py +0 -4
  36. enigmapython-1.3.2/enigmapython/Utils.py +0 -51
  37. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Alphabets.py +0 -0
  38. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Clonable.py +0 -0
  39. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaB_A133.py +0 -0
  40. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaB_A133Etw.py +0 -0
  41. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaB_A133RotorI.py +0 -0
  42. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaB_A133RotorII.py +0 -0
  43. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaB_A133RotorIII.py +0 -0
  44. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaD.py +0 -0
  45. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaDRotorI.py +0 -0
  46. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaDRotorII.py +0 -0
  47. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaDRotorIII.py +0 -0
  48. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaI.py +0 -0
  49. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaINorway.py +0 -0
  50. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaINorwayRotorI.py +0 -0
  51. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaINorwayRotorII.py +0 -0
  52. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaINorwayRotorIII.py +0 -0
  53. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaINorwayRotorIV.py +0 -0
  54. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaINorwayRotorV.py +0 -0
  55. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaIRotorI.py +0 -0
  56. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaIRotorII.py +0 -0
  57. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaIRotorIII.py +0 -0
  58. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaIRotorIV.py +0 -0
  59. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaIRotorV.py +0 -0
  60. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaISonder.py +0 -0
  61. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaISonderRotorI.py +0 -0
  62. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaISonderRotorII.py +0 -0
  63. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaISonderRotorIII.py +0 -0
  64. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM3.py +0 -0
  65. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM3RotorI.py +0 -0
  66. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM3RotorII.py +0 -0
  67. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM3RotorIII.py +0 -0
  68. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM3RotorIV.py +0 -0
  69. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM3RotorV.py +0 -0
  70. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM3RotorVI.py +0 -0
  71. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM3RotorVII.py +0 -0
  72. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM3RotorVIII.py +0 -0
  73. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4.py +0 -0
  74. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorBeta.py +0 -0
  75. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorGamma.py +0 -0
  76. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorI.py +0 -0
  77. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorII.py +0 -0
  78. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorIII.py +0 -0
  79. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorIV.py +0 -0
  80. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorV.py +0 -0
  81. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorVI.py +0 -0
  82. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorVII.py +0 -0
  83. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaM4RotorVIII.py +0 -0
  84. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaZ.py +0 -0
  85. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaZEtw.py +0 -0
  86. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaZRotorI.py +0 -0
  87. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaZRotorII.py +0 -0
  88. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EnigmaZRotorIII.py +0 -0
  89. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/EtwPassthrough.py +0 -0
  90. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Journaled.py +0 -0
  91. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Observable.py +0 -0
  92. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Observer.py +0 -0
  93. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/PlugboardPassthrough.py +0 -0
  94. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Reflector.py +0 -0
  95. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/Swappable.py +0 -0
  96. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/SwappablePlugboard.py +0 -0
  97. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython/__init__.py +0 -0
  98. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython.egg-info/dependency_links.txt +0 -0
  99. {enigmapython-1.3.2 → enigmapython-2.0.0}/enigmapython.egg-info/top_level.txt +0 -0
  100. {enigmapython-1.3.2 → enigmapython-2.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: enigmapython
3
- Version: 1.3.2
3
+ Version: 2.0.0
4
4
  Summary: A simple yet faithful library to emulate different Enigma machines models using Python
5
5
  Home-page: https://github.com/denismaggior8/enigma-python
6
6
  Author: Denis Maggiorotto
@@ -27,6 +27,15 @@ Dynamic: summary
27
27
 
28
28
  Welcome to **enigmapython**, a Python package designed to emulate the legendary Enigma cryptographic machine used during World War II. **enigmapython** provides a faithful implementation of the Enigma machine, allowing users to explore and understand the workings of this historic device.
29
29
 
30
+ <br>
31
+ <div class="img-container" style="text-align: center;">
32
+ <a href="https://asciinema.org/a/761182">
33
+ <img src="https://asciinema.org/a/761182.svg" alt="asciicast"/>
34
+ </a>
35
+ </div>
36
+ <br>
37
+
38
+
30
39
  This project is listed on [Wikipedia](https://en.wikipedia.org/wiki/List_of_Enigma_machine_simulators) as a globally recognized Enigma machine simulator, noted for its historical accuracy.
31
40
 
32
41
  <div class="img-container" style="text-align: center;">
@@ -65,6 +74,7 @@ This project is listed on [Wikipedia](https://en.wikipedia.org/wiki/List_of_Enig
65
74
 
66
75
 
67
76
  - [Authentic German Army Test Message from 1930](https://cryptocellar.org/enigma/e-message-1930.html): Documented by Frode Weierud's CryptoCellar, this test validates that the **Enigma I** implementation correctly handles the complex interaction of rotors, ring settings, and plugboard connections exactly as the original machines did. See the [corresponding unit test](https://github.com/denismaggior8/enigma-python/blob/master/tests/Enigma1930_tests.py) for details.
77
+ - [Authentic U-534 M4 Message P1030700](https://enigma.hoerenberg.com/index.php?cat=The%20U534%20messages&page=P1030700): A message from the U-534 submarine (May 1945), validating the **Enigma M4** implementation (including the Greek rotor and thin reflector) against historical intercepts. See the [corresponding unit test](https://github.com/denismaggior8/enigma-python/blob/master/tests/EnigmaM4_U534_tests.py) for details.
68
78
 
69
79
  ## Machines implementations
70
80
 
@@ -82,12 +92,22 @@ The following Enigma machine models (along with their rotors, reflectors and plu
82
92
 
83
93
  *given the rarity of this model and the little documentation/simulators available, although I expect an encryption consistency on par with newer models, I was unable to test it as I would have liked
84
94
 
95
+ ### Enigma K (Commercial Enigma)
96
+
97
+ | Scrambler | Wiring | Notch | Implemented |
98
+ |------- |---------------------------- |------- |------------- |
99
+ | ETW "QWERTZ" | qwertzuioasdfghjkpyxcvbnml | N/A | ✅ |
100
+ | Rotor I | lpgszmhaeoqkvxrfybutnicjdw | y | ✅ |
101
+ | Rotor II | slvgbtfxjqohewirzyamkpcndu | e | ✅ |
102
+ | Rotor III | cjgdpshkturawzxfmynqobvlie | n | ✅ |
103
+ | Reflector UKW | imetcgfraysqbzxwlhkdvupojn | N/A | ✅ |
104
+
105
+
85
106
  ### Enigma D
86
107
 
87
108
  | Scrambler | Wiring | Notch | Implemented |
88
109
  |------- |---------------------------- |------- |------------- |
89
110
  | ETW "QWERTZ" | qwertzuioasdfghjkpyxcvbnml | N/A | ✅ |
90
- | ETW "JWULCM" | jwulcmnohpqzyxiradkegvbtsf | N/A | ✅ |
91
111
  | Rotor I | lpgszmhaeoqkvxrfybutnicjdw | y | ✅ |
92
112
  | Rotor II | slvgbtfxjqohewirzyamkpcndu | e | ✅ |
93
113
  | Rotor III | bdfhjlcprtxvznyeiwgakmusqo | n | ✅ |
@@ -191,7 +211,9 @@ Get started by installing the package from PyPI (`pip install enigmapython`) and
191
211
 
192
212
  ## Documentation
193
213
 
194
- An initial documentation draft can be found [here](./docs/README.md), but in most cases examples, code (and comments) are better than the documentation
214
+ Full API documentation is available on [ReadTheDocs](https://enigmapython.readthedocs.io/).
215
+
216
+ For additional details, you can also refer to the [local documentation](./docs/README.md), examples, and code comments.
195
217
 
196
218
  ## Known implementations
197
219
 
@@ -3,6 +3,7 @@ from .RotatingReflector import RotatingReflector
3
3
  from .Alphabets import Alphabets
4
4
  from .Journaled import Journaled
5
5
  from .Clonable import Clonable
6
+ from .Utils import Utils
6
7
  import logging
7
8
 
8
9
 
@@ -41,6 +42,7 @@ class Enigma(Observer,Journaled,Clonable):
41
42
  return output_string
42
43
 
43
44
  def input_char(self,char):
45
+ char = char.lower()
44
46
  logging.info("Input char: {}".format(char))
45
47
  ## Triggering rotors extra rotation due to double step issue
46
48
  for rotor in self.rotors:
@@ -75,7 +77,11 @@ class Enigma(Observer,Journaled,Clonable):
75
77
  def process_char(self, char):
76
78
  scrambled_char = self.plugboard.scramble_char(self.plugboard.wiring,self.plugboard.alphabet_list.index(char),0)
77
79
  logging.debug("Scrambled letter from plugboard: {}".format(scrambled_char))
78
- scrambled_char = self.etw.scramble_char(self.etw.wiring,self.alphabet_list.index(scrambled_char), 0)
80
+
81
+ # Calculate inverted wiring for ETW
82
+ inverted_wiring = Utils.inverse_string_permutation(self.etw.wiring, ''.join(self.etw.alphabet_list))
83
+
84
+ scrambled_char = self.etw.scramble_char(inverted_wiring, self.alphabet_list.index(scrambled_char), 0)
79
85
  logging.debug("Scrambled letter from ETW: {}".format(scrambled_char))
80
86
  iteration = 0
81
87
  for rotor in self.rotors:
@@ -85,8 +91,14 @@ class Enigma(Observer,Journaled,Clonable):
85
91
  scrambled_char = rotor.scramble_char(rotor.wiring,self.alphabet_list.index(scrambled_char)-self.rotors[iteration-1].position, rotor.position)
86
92
  iteration +=1
87
93
  logging.debug("Scrambled letter from rotor{}: {}".format(str(iteration),scrambled_char))
88
- scrambled_char = self.reflector.scramble_char(self.reflector.wiring,(self.alphabet_list.index(scrambled_char)-self.rotors[iteration-1].position), 0)
94
+ reflector_pos = getattr(self.reflector, 'position', 0)
95
+ scrambled_char = self.reflector.scramble_char(self.reflector.wiring,(self.alphabet_list.index(scrambled_char)-self.rotors[iteration-1].position), reflector_pos)
89
96
  logging.debug("Scrambled letter from reflector: {}".format(scrambled_char))
97
+
98
+ # Adjust for reflector position on return path
99
+ if reflector_pos != 0:
100
+ scrambled_char = self.shift_letter(scrambled_char, -reflector_pos, self.alphabet_list)
101
+
90
102
  for rotor in reversed(self.rotors):
91
103
  if iteration == len(self.rotors):
92
104
  scrambled_char = rotor.scramble_char(self.alphabet_list,(rotor.wiring.index(self.shift_letter(scrambled_char,rotor.position,self.alphabet_list))-rotor.position), rotor.position)
@@ -96,7 +108,8 @@ class Enigma(Observer,Journaled,Clonable):
96
108
  logging.debug("Scrambled letter from rotor{}: {}".format(str(iteration+1),scrambled_char))
97
109
 
98
110
  # Processing rotor 1 returning signal by ETW
99
- scrambled_char = self.etw.scramble_char(self.alphabet_list,(self.etw.wiring.index(self.shift_letter(scrambled_char, (0 - self.rotors[iteration].position),self.alphabet_list))), 0)
111
+ # Processing rotor 1 returning signal by ETW
112
+ scrambled_char = self.etw.scramble_char(self.alphabet_list,(inverted_wiring.index(self.shift_letter(scrambled_char, (0 - self.rotors[iteration].position),self.alphabet_list))), 0)
100
113
  logging.debug("Scrambled letter from ETW: {}".format(scrambled_char))
101
114
 
102
115
  scrambled_char = self.plugboard.scramble_char(self.plugboard.wiring,self.plugboard.alphabet_list.index(scrambled_char),0)
@@ -0,0 +1,5 @@
1
+ from .EtwQWERTZ import EtwQWERTZ
2
+
3
+ class EnigmaDEtw_QWERTZ(EtwQWERTZ):
4
+ """Enigma D ETW with QWERTZ keyboard layout"""
5
+ pass
@@ -0,0 +1,13 @@
1
+ from .Enigma import Enigma
2
+ from .PlugboardPassthrough import PlugboardPassthrough
3
+
4
+ class EnigmaK(Enigma):
5
+ def __init__(self, rotor1, rotor2, rotor3, reflector, etw, auto_increment_rotors=True):
6
+ super().__init__(
7
+ plugboard=PlugboardPassthrough(),
8
+ rotors=[rotor1, rotor2, rotor3],
9
+ reflector=reflector,
10
+ etw=etw,
11
+ auto_increment_rotors=auto_increment_rotors
12
+ )
13
+
@@ -0,0 +1,5 @@
1
+ from .EtwQWERTZ import EtwQWERTZ
2
+
3
+ class EnigmaKEtw_QWERTZ(EtwQWERTZ):
4
+ """Enigma K ETW with QWERTZ keyboard layout"""
5
+ pass
@@ -0,0 +1,18 @@
1
+ from .Rotor import Rotor
2
+ from .Alphabets import Alphabets
3
+
4
+ class EnigmaKRotorI(Rotor):
5
+
6
+ wiring = 'lpgszmhaeoqkvxrfybutnicjdw'
7
+ notch_indexes = [24] # Y (notch position)
8
+ tag = "K_I"
9
+
10
+ def __init__(self, position = 0, ring = 0):
11
+ super().__init__(
12
+ wiring = self.wiring,
13
+ position=position,
14
+ ring=ring,
15
+ notch_indexes=self.notch_indexes,
16
+ alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
17
+ )
18
+
@@ -0,0 +1,18 @@
1
+ from .Rotor import Rotor
2
+ from .Alphabets import Alphabets
3
+
4
+ class EnigmaKRotorII(Rotor):
5
+
6
+ wiring = 'slvgbtfxjqohewirzyamkpcndu'
7
+ notch_indexes = [4] # E (notch position)
8
+ tag = "K_II"
9
+
10
+ def __init__(self, position = 0, ring = 0):
11
+ super().__init__(
12
+ wiring = self.wiring,
13
+ position=position,
14
+ ring=ring,
15
+ notch_indexes=self.notch_indexes,
16
+ alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
17
+ )
18
+
@@ -0,0 +1,18 @@
1
+ from .Rotor import Rotor
2
+ from .Alphabets import Alphabets
3
+
4
+ class EnigmaKRotorIII(Rotor):
5
+
6
+ wiring = 'cjgdpshkturawzxfmynqobvlie'
7
+ notch_indexes = [13] # N (position 13)
8
+ tag = "K_III"
9
+
10
+ def __init__(self, position = 0, ring = 0):
11
+ super().__init__(
12
+ wiring = self.wiring,
13
+ position=position,
14
+ ring=ring,
15
+ notch_indexes=self.notch_indexes,
16
+ alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
17
+ )
18
+
@@ -9,7 +9,6 @@ class Etw(Scrambler):
9
9
  def __init__(self, wiring, alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")):
10
10
  super().__init__(
11
11
  alphabet=alphabet,
12
- ring=0,
13
12
  wiring=wiring
14
13
  )
15
14
  self.wiring = wiring
@@ -0,0 +1,7 @@
1
+ from .Etw import Etw
2
+
3
+ class EtwQWERTZ(Etw):
4
+ """Shared QWERTZ keyboard layout ETW used by Enigma D and Enigma K"""
5
+ wiring = "qwertzuioasdfghjkpyxcvbnml"
6
+ def __init__(self):
7
+ super().__init__(self.wiring)
@@ -11,7 +11,6 @@ class Plugboard(Scrambler):
11
11
  self.alphabet_list = list(alphabet)
12
12
  super().__init__(
13
13
  alphabet=alphabet,
14
- ring=0,
15
14
  wiring=wiring
16
15
  )
17
16
 
@@ -8,6 +8,5 @@ class ReflectorUKWA(Reflector):
8
8
  def __init__(self):
9
9
  super().__init__(
10
10
  wiring=self.wiring,
11
- ring=0,
12
11
  alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
13
12
  )
@@ -8,6 +8,5 @@ class ReflectorUKWB(Reflector):
8
8
  def __init__(self):
9
9
  super().__init__(
10
10
  wiring=self.wiring,
11
- ring=0,
12
11
  alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
13
12
  )
@@ -8,6 +8,5 @@ class ReflectorUKWBThin(Reflector):
8
8
  def __init__(self):
9
9
  super().__init__(
10
10
  wiring=self.wiring,
11
- ring=0,
12
11
  alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
13
12
  )
@@ -10,6 +10,5 @@ class ReflectorUKWC(Reflector):
10
10
  def __init__(self):
11
11
  super().__init__(
12
12
  wiring=self.wiring,
13
- ring=0,
14
13
  alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
15
14
  )
@@ -10,6 +10,5 @@ class ReflectorUKWCThin(Reflector):
10
10
  def __init__(self):
11
11
  super().__init__(
12
12
  wiring=self.wiring,
13
- ring=0,
14
13
  alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
15
14
  )
@@ -10,6 +10,5 @@ class ReflectorUKW_EnigmaB_A133(Reflector):
10
10
  def __init__(self):
11
11
  super().__init__(
12
12
  self.wiring,
13
- ring=0,
14
13
  alphabet=Alphabets.lookup.get("enigma_b_a133_28chars_lowercase")
15
14
  )
@@ -0,0 +1,15 @@
1
+ from .SettableReflector import SettableReflector
2
+ from .Alphabets import Alphabets
3
+
4
+ class ReflectorUKW_EnigmaCommercial(SettableReflector):
5
+ """Shared UKW reflector used by commercial Enigma D and Enigma K"""
6
+
7
+ wiring = 'imetcgfraysqbzxwlhkdvupojn'
8
+
9
+ def __init__(self, position=0, ring=0):
10
+ super().__init__(
11
+ wiring=self.wiring,
12
+ ring=ring,
13
+ position=position,
14
+ alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
15
+ )
@@ -1,14 +1,13 @@
1
1
  from .Rotor import Rotor
2
2
  from .Reflector import Reflector
3
3
  from .Alphabets import Alphabets
4
- class ReflectorNorwayUKW(Reflector):
4
+ class ReflectorUKW_EnigmaINorway(Reflector):
5
5
 
6
6
  wiring = 'mowjypuxndsraibfvlkzgqchet'
7
- tag = "IN_UKW"
7
+ tag = "Norway_UKW"
8
8
 
9
9
  def __init__(self):
10
10
  super().__init__(
11
11
  wiring=self.wiring,
12
- ring=0,
13
12
  alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
14
13
  )
@@ -1,13 +1,12 @@
1
1
  from .Alphabets import Alphabets
2
2
  from .Reflector import Reflector
3
- class ReflectorSonderUKW(Reflector):
3
+ class ReflectorUKW_EnigmaISonder(Reflector):
4
4
 
5
5
  wiring = 'ciagsndrbytpzfulvhekoqxwjm'
6
- tag = "IS_UKW"
6
+ tag = "Sonder_UKW"
7
7
 
8
8
  def __init__(self):
9
9
  super().__init__(
10
10
  wiring=self.wiring,
11
- ring=0,
12
11
  alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
13
12
  )
@@ -4,7 +4,7 @@ from .Alphabets import Alphabets
4
4
  from .RotatingReflector import RotatingReflector
5
5
 
6
6
 
7
- class ReflectorZUKW(RotatingReflector):
7
+ class ReflectorUKW_EnigmaZ(RotatingReflector):
8
8
 
9
9
  wiring = '5079183642'
10
10
  notch_indexes = [3]
@@ -0,0 +1,5 @@
1
+ from .Rotor import Rotor
2
+ from .Reflector import Reflector
3
+
4
+ class RotatingReflector(Rotor, Reflector):
5
+ pass
@@ -1,16 +1,13 @@
1
1
  from .Observable import Observable
2
2
  from .Scrambler import Scrambler
3
+ from .Settable import Settable
3
4
  import logging
4
5
 
5
- class Rotor(Scrambler,Observable):
6
- position = None
6
+ class Rotor(Scrambler,Observable,Settable):
7
7
  rotations_counter = None
8
8
  notch_indexes = None
9
9
  double_step_triggered = None
10
10
 
11
- def reset_position(self):
12
- self.position = 0
13
-
14
11
  def increment_position(self):
15
12
  self.position = ((self.position + 1) % len(self.wiring))
16
13
  self.rotations_counter = self.rotations_counter + 1
@@ -21,18 +18,18 @@ class Rotor(Scrambler,Observable):
21
18
  self.notify_observers(None,None)
22
19
 
23
20
  def set_position(self,position):
24
- self.position = position % len(self.wiring)
21
+ super().set_position(position % len(self.wiring))
25
22
  self.rotations_counter = 0
26
23
 
27
24
  def __init__(self, wiring, notch_indexes, alphabet, position = 0, ring = 0):
28
25
  # Scrambler properties
29
- super().__init__(
30
- wiring = wiring,
31
- ring = ring,
32
- alphabet = alphabet
33
- )
26
+ # Scrambler properties
27
+ Scrambler.__init__(self, wiring=wiring, alphabet=alphabet)
28
+ # Settable properties
29
+ Settable.__init__(self, position=position % len(wiring), ring=ring)
30
+ self.set_ring(self.ring)
31
+
34
32
  # Rotor properties
35
- self.position = position % len(wiring)
36
33
  self.notch_indexes = notch_indexes
37
34
  self.double_step_triggered = False
38
35
  self.rotations_counter = 0
@@ -0,0 +1,30 @@
1
+ import logging
2
+ from .Journaled import Journaled
3
+
4
+ class Scrambler(Journaled):
5
+ wiring = None
6
+ alphabet_list = None
7
+ original_wiring = None
8
+
9
+ def scramble_char(self, dictionary, letter_index, shift):
10
+ output_char_index = dictionary.index(dictionary[(shift + letter_index) % len(dictionary)])
11
+ output_char = dictionary[output_char_index]
12
+ super().append_to_journal({
13
+ 'output_char': output_char
14
+ })
15
+ return output_char
16
+
17
+ def __init__(self, wiring, alphabet):
18
+ #Journaled.__init__(self)
19
+ super().__init__()
20
+ self.wiring = wiring
21
+ self.original_wiring = self.wiring
22
+ self.alphabet_list = list(alphabet)
23
+
24
+ def __str__(self):
25
+ str = ""
26
+ for char in self.alphabet_list:
27
+ str += char
28
+ str += "\n"
29
+ str += "|" * len(self.alphabet_list)
30
+ return str
@@ -1,35 +1,25 @@
1
- import logging
2
- from .Journaled import Journaled
3
-
4
- class Scrambler(Journaled):
5
- wiring = None
6
- alphabet_list = None
7
- dot_position = None
8
- ring = None
9
- set_scrambler_ring = None
10
- original_wiring = None
11
-
12
- def scramble_char(self, dictionary, letter_index, shift):
13
- output_char_index = dictionary.index(dictionary[(shift + letter_index) % len(dictionary)])
14
- output_char = dictionary[output_char_index]
15
- super().append_to_journal({
16
- 'output_char': output_char
17
- })
18
- return output_char
1
+ class Settable:
2
+ position = 0
3
+ ring = 0
19
4
 
20
- def __init__(self, wiring, alphabet, ring):
21
- #Journaled.__init__(self)
22
- super().__init__()
23
- self.wiring = wiring
24
- self.original_wiring = self.wiring
25
- self.alphabet_list = list(alphabet)
5
+ def __init__(self, position=0, ring=0):
6
+ self.position = position
26
7
  self.ring = ring
27
- self.set_scrambler_ring(ring)
8
+
9
+ def set_position(self, position):
10
+ self.position = position
28
11
 
12
+ def reset_position(self):
13
+ self.position = 0
29
14
 
30
- def set_scrambler_ring(self, ring):
15
+ def reset_ring(self):
16
+ self.set_ring(0)
17
+
18
+ def set_ring(self, ring):
19
+ self.ring = ring
31
20
  self.wiring = self.original_wiring
32
21
  self.dot_position = list(self.wiring).index(self.alphabet_list[0])
22
+ import logging
33
23
  logging.debug("Dot position: " + str(self.dot_position))
34
24
  for i in range(0, ring):
35
25
  # Set temporary wiring variable
@@ -39,7 +29,7 @@ class Scrambler(Journaled):
39
29
  # Loop over chars in temporary wiring
40
30
  for char in temp_wiring:
41
31
  # Shift the char by one and add that shifted char to wiring variable
42
- wiring += Scrambler.__shift(char, 1, self.alphabet_list)
32
+ wiring += self._shift(char, 1, self.alphabet_list)
43
33
  # Add one to dot position, make sure we don't exceed the lenght of the alphabet
44
34
  self.wiring = wiring
45
35
  self.dot_position = (self.dot_position + 1) % len(self.alphabet_list)
@@ -53,17 +43,9 @@ class Scrambler(Journaled):
53
43
  self.wiring = self.wiring[-1:] + self.wiring[:-1]
54
44
  # The following line has been commente out becode not Micropython compatible
55
45
  # logging.debug("Rotation " + str(i).zfill(2) + "; Wiring: " + self.wiring)
56
-
46
+
57
47
  @staticmethod
58
- def __shift(letter, shift, alphabet_list):
48
+ def _shift(letter, shift, alphabet_list):
59
49
  for i in range(0, len(alphabet_list)):
60
50
  if alphabet_list[i] == letter:
61
51
  return alphabet_list[(i + shift) % len(alphabet_list)]
62
-
63
- def __str__(self):
64
- str = ""
65
- for char in self.alphabet_list:
66
- str += char
67
- str += "\n"
68
- str += "|" * len(self.alphabet_list)
69
- return str
@@ -0,0 +1,8 @@
1
+ from .Reflector import Reflector
2
+ from .Settable import Settable
3
+
4
+ class SettableReflector(Reflector, Settable):
5
+ def __init__(self, wiring, alphabet, position=0, ring=0):
6
+ Reflector.__init__(self, wiring=wiring, alphabet=alphabet)
7
+ Settable.__init__(self, position=position, ring=ring)
8
+ self.set_ring(self.ring)
@@ -0,0 +1,85 @@
1
+
2
+ class Utils:
3
+
4
+
5
+ @staticmethod
6
+ def find_divergence(str1, str2):
7
+ """
8
+ Finds the index where two strings first diverge,
9
+ and the characters at that index in both strings.
10
+
11
+ Args:
12
+ str1 (str): The first string.
13
+ str2 (str): The second string.
14
+
15
+ Returns:
16
+ tuple: (index, char1, char2) where index is the position of divergence,
17
+ char1 is the character in str1 at the divergence,
18
+ and char2 is the character in str2 at the divergence.
19
+ If the strings only diverge in length, char1 and char2 will be None.
20
+ If the strings are identical, returns None.
21
+ """
22
+ # Iterate through both strings up to the length of the shorter string
23
+ for i in range(min(len(str1), len(str2))):
24
+ if str1[i] != str2[i]:
25
+ return i, str1[i], str2[i] # Return the index and differing characters
26
+
27
+ # If the strings differ in length
28
+ if len(str1) != len(str2):
29
+ return min(len(str1), len(str2)), None, None
30
+
31
+ # If the strings are identical
32
+ return None
33
+
34
+ @staticmethod
35
+ def find_all_subclasses(cls):
36
+ return set(cls.__subclasses__()).union(
37
+ [s for c in cls.__subclasses__() for s in Utils.find_all_subclasses(c)])
38
+
39
+ @staticmethod
40
+ def swap_chars(string, ch1, ch2):
41
+ if ch1 == ch2: return string
42
+ str_list = []
43
+ for char in string:
44
+ if char == ch1:
45
+ str_list.append(ch2)
46
+ elif char == ch2:
47
+ str_list.append(ch1)
48
+ else:
49
+ str_list.append(char)
50
+ return ''.join(str_list)
51
+
52
+ @staticmethod
53
+ def inverse_string_permutation(string: str, alphabet: str = None) -> str:
54
+ """
55
+ Compute the inverse permutation of a string for any alphabet.
56
+
57
+ string : str
58
+ A string mapping input -> output
59
+ alphabet : str, optional
60
+ The alphabet to use. Defaults to the characters in string length,
61
+ starting with the first character of string (e.g., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
62
+
63
+ Returns
64
+ -------
65
+ str
66
+ The inverse string
67
+ """
68
+ n = len(string)
69
+
70
+ # If no alphabet is provided, assume consecutive characters starting from string[0]
71
+ if alphabet is None:
72
+ alphabet = ''.join(chr(ord(string[0]) + i) for i in range(n))
73
+
74
+ # Build a dictionary for quick index lookup
75
+ char_to_index = {char: i for i, char in enumerate(alphabet)}
76
+
77
+ # Initialize inverse list
78
+ inverse = [''] * n
79
+
80
+ for i, out_char in enumerate(string):
81
+ index = char_to_index[out_char]
82
+ inverse[index] = alphabet[i]
83
+
84
+ return ''.join(inverse)
85
+
@@ -1,7 +1,7 @@
1
1
  from rich.console import Console
2
2
  from rich.text import Text
3
3
 
4
- from enigmapython.RotatingReflector import RotatingReflector
4
+ from enigmapython.SettableReflector import SettableReflector
5
5
 
6
6
  class XRay():
7
7
  @staticmethod
@@ -56,8 +56,8 @@ class XRay():
56
56
  | | {rotor_walls} | | | |
57
57
  +-----+ {rotor_walls_bottom} +-----+ +-----+
58
58
 
59
- Pos.: {"{} ({:02})".format(enigma.alphabet_list[enigma.reflector.position].upper(),enigma.reflector.position) if isinstance(enigma.reflector, RotatingReflector) else ' N/A '} {rotors_positions}
60
- Ring: {"{} ({:02})".format(enigma.alphabet_list[enigma.reflector.ring].upper(),enigma.reflector.ring)} {rotors_rings}
59
+ Pos.: {"{} ({:02})".format(enigma.alphabet_list[enigma.reflector.position].upper(),enigma.reflector.position) if isinstance(enigma.reflector, SettableReflector) else ' N/A '} {rotors_positions}
60
+ Ring: {"{} ({:02})".format(enigma.alphabet_list[enigma.reflector.ring].upper(),enigma.reflector.ring) if isinstance(enigma.reflector, SettableReflector) else ' N/A '} {rotors_rings}
61
61
  """
62
62
  console.print(Text(diagram, style="bold"))
63
63
  return Text(diagram, style="bold")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: enigmapython
3
- Version: 1.3.2
3
+ Version: 2.0.0
4
4
  Summary: A simple yet faithful library to emulate different Enigma machines models using Python
5
5
  Home-page: https://github.com/denismaggior8/enigma-python
6
6
  Author: Denis Maggiorotto
@@ -27,6 +27,15 @@ Dynamic: summary
27
27
 
28
28
  Welcome to **enigmapython**, a Python package designed to emulate the legendary Enigma cryptographic machine used during World War II. **enigmapython** provides a faithful implementation of the Enigma machine, allowing users to explore and understand the workings of this historic device.
29
29
 
30
+ <br>
31
+ <div class="img-container" style="text-align: center;">
32
+ <a href="https://asciinema.org/a/761182">
33
+ <img src="https://asciinema.org/a/761182.svg" alt="asciicast"/>
34
+ </a>
35
+ </div>
36
+ <br>
37
+
38
+
30
39
  This project is listed on [Wikipedia](https://en.wikipedia.org/wiki/List_of_Enigma_machine_simulators) as a globally recognized Enigma machine simulator, noted for its historical accuracy.
31
40
 
32
41
  <div class="img-container" style="text-align: center;">
@@ -65,6 +74,7 @@ This project is listed on [Wikipedia](https://en.wikipedia.org/wiki/List_of_Enig
65
74
 
66
75
 
67
76
  - [Authentic German Army Test Message from 1930](https://cryptocellar.org/enigma/e-message-1930.html): Documented by Frode Weierud's CryptoCellar, this test validates that the **Enigma I** implementation correctly handles the complex interaction of rotors, ring settings, and plugboard connections exactly as the original machines did. See the [corresponding unit test](https://github.com/denismaggior8/enigma-python/blob/master/tests/Enigma1930_tests.py) for details.
77
+ - [Authentic U-534 M4 Message P1030700](https://enigma.hoerenberg.com/index.php?cat=The%20U534%20messages&page=P1030700): A message from the U-534 submarine (May 1945), validating the **Enigma M4** implementation (including the Greek rotor and thin reflector) against historical intercepts. See the [corresponding unit test](https://github.com/denismaggior8/enigma-python/blob/master/tests/EnigmaM4_U534_tests.py) for details.
68
78
 
69
79
  ## Machines implementations
70
80
 
@@ -82,12 +92,22 @@ The following Enigma machine models (along with their rotors, reflectors and plu
82
92
 
83
93
  *given the rarity of this model and the little documentation/simulators available, although I expect an encryption consistency on par with newer models, I was unable to test it as I would have liked
84
94
 
95
+ ### Enigma K (Commercial Enigma)
96
+
97
+ | Scrambler | Wiring | Notch | Implemented |
98
+ |------- |---------------------------- |------- |------------- |
99
+ | ETW "QWERTZ" | qwertzuioasdfghjkpyxcvbnml | N/A | ✅ |
100
+ | Rotor I | lpgszmhaeoqkvxrfybutnicjdw | y | ✅ |
101
+ | Rotor II | slvgbtfxjqohewirzyamkpcndu | e | ✅ |
102
+ | Rotor III | cjgdpshkturawzxfmynqobvlie | n | ✅ |
103
+ | Reflector UKW | imetcgfraysqbzxwlhkdvupojn | N/A | ✅ |
104
+
105
+
85
106
  ### Enigma D
86
107
 
87
108
  | Scrambler | Wiring | Notch | Implemented |
88
109
  |------- |---------------------------- |------- |------------- |
89
110
  | ETW "QWERTZ" | qwertzuioasdfghjkpyxcvbnml | N/A | ✅ |
90
- | ETW "JWULCM" | jwulcmnohpqzyxiradkegvbtsf | N/A | ✅ |
91
111
  | Rotor I | lpgszmhaeoqkvxrfybutnicjdw | y | ✅ |
92
112
  | Rotor II | slvgbtfxjqohewirzyamkpcndu | e | ✅ |
93
113
  | Rotor III | bdfhjlcprtxvznyeiwgakmusqo | n | ✅ |
@@ -191,7 +211,9 @@ Get started by installing the package from PyPI (`pip install enigmapython`) and
191
211
 
192
212
  ## Documentation
193
213
 
194
- An initial documentation draft can be found [here](./docs/README.md), but in most cases examples, code (and comments) are better than the documentation
214
+ Full API documentation is available on [ReadTheDocs](https://enigmapython.readthedocs.io/).
215
+
216
+ For additional details, you can also refer to the [local documentation](./docs/README.md), examples, and code comments.
195
217
 
196
218
  ## Known implementations
197
219
 
@@ -8,7 +8,6 @@ enigmapython/EnigmaB_A133RotorI.py
8
8
  enigmapython/EnigmaB_A133RotorII.py
9
9
  enigmapython/EnigmaB_A133RotorIII.py
10
10
  enigmapython/EnigmaD.py
11
- enigmapython/EnigmaDEtw_JWULCM.py
12
11
  enigmapython/EnigmaDEtw_QWERTZ.py
13
12
  enigmapython/EnigmaDRotorI.py
14
13
  enigmapython/EnigmaDRotorII.py
@@ -29,6 +28,11 @@ enigmapython/EnigmaISonder.py
29
28
  enigmapython/EnigmaISonderRotorI.py
30
29
  enigmapython/EnigmaISonderRotorII.py
31
30
  enigmapython/EnigmaISonderRotorIII.py
31
+ enigmapython/EnigmaK.py
32
+ enigmapython/EnigmaKEtw_QWERTZ.py
33
+ enigmapython/EnigmaKRotorI.py
34
+ enigmapython/EnigmaKRotorII.py
35
+ enigmapython/EnigmaKRotorIII.py
32
36
  enigmapython/EnigmaM3.py
33
37
  enigmapython/EnigmaM3RotorI.py
34
38
  enigmapython/EnigmaM3RotorII.py
@@ -56,25 +60,28 @@ enigmapython/EnigmaZRotorII.py
56
60
  enigmapython/EnigmaZRotorIII.py
57
61
  enigmapython/Etw.py
58
62
  enigmapython/EtwPassthrough.py
63
+ enigmapython/EtwQWERTZ.py
59
64
  enigmapython/Journaled.py
60
65
  enigmapython/Observable.py
61
66
  enigmapython/Observer.py
62
67
  enigmapython/Plugboard.py
63
68
  enigmapython/PlugboardPassthrough.py
64
69
  enigmapython/Reflector.py
65
- enigmapython/ReflectorDUKW.py
66
- enigmapython/ReflectorNorwayUKW.py
67
- enigmapython/ReflectorSonderUKW.py
68
70
  enigmapython/ReflectorUKWA.py
69
71
  enigmapython/ReflectorUKWB.py
70
72
  enigmapython/ReflectorUKWBThin.py
71
73
  enigmapython/ReflectorUKWC.py
72
74
  enigmapython/ReflectorUKWCThin.py
73
75
  enigmapython/ReflectorUKW_EnigmaB_A133.py
74
- enigmapython/ReflectorZUKW.py
76
+ enigmapython/ReflectorUKW_EnigmaCommercial.py
77
+ enigmapython/ReflectorUKW_EnigmaINorway.py
78
+ enigmapython/ReflectorUKW_EnigmaISonder.py
79
+ enigmapython/ReflectorUKW_EnigmaZ.py
75
80
  enigmapython/RotatingReflector.py
76
81
  enigmapython/Rotor.py
77
82
  enigmapython/Scrambler.py
83
+ enigmapython/Settable.py
84
+ enigmapython/SettableReflector.py
78
85
  enigmapython/Swappable.py
79
86
  enigmapython/SwappablePlugboard.py
80
87
  enigmapython/Utils.py
@@ -12,7 +12,7 @@ setup(
12
12
  long_description_content_type='text/markdown',
13
13
  url="https://github.com/denismaggior8/enigma-python",
14
14
  name="enigmapython",
15
- version="1.3.2",
15
+ version="2.0.0",
16
16
  packages=find_packages(
17
17
  # All keyword arguments below are optional:
18
18
  where='.', # '.' by default
@@ -1,6 +0,0 @@
1
- from .Etw import Etw
2
-
3
- class EnigmaDEtw_JWULCM(Etw):
4
- wiring = "jwulcmnohpqzyxiradkegvbtsf"
5
- def __init__(self):
6
- super().__init__(self.wiring)
@@ -1,6 +0,0 @@
1
- from .Etw import Etw
2
-
3
- class EnigmaDEtw_QWERTZ(Etw):
4
- wiring = "qwertzuioasdfghjkpyxcvbnml"
5
- def __init__(self):
6
- super().__init__(self.wiring)
@@ -1,13 +0,0 @@
1
- from .Reflector import Reflector
2
- from .Alphabets import Alphabets
3
- class ReflectorDUKW(Reflector):
4
-
5
- wiring = 'imetcgfraysqbzxwlhkdvupojn'
6
- tag = "D_UKW"
7
-
8
- def __init__(self):
9
- super().__init__(
10
- wiring=self.wiring,
11
- ring=0,
12
- alphabet=Alphabets.lookup.get("latin_i18n_26chars_lowercase")
13
- )
@@ -1,4 +0,0 @@
1
- from .Rotor import Rotor
2
-
3
- class RotatingReflector(Rotor):
4
- None
@@ -1,51 +0,0 @@
1
-
2
- class Utils:
3
-
4
-
5
- @staticmethod
6
- def find_divergence(str1, str2):
7
- """
8
- Finds the index where two strings first diverge,
9
- and the characters at that index in both strings.
10
-
11
- Parameters:
12
- str1 (str): The first string.
13
- str2 (str): The second string.
14
-
15
- Returns:
16
- tuple: (index, char1, char2) where index is the position of divergence,
17
- char1 is the character in str1 at the divergence,
18
- and char2 is the character in str2 at the divergence.
19
- If the strings only diverge in length, char1 and char2 will be None.
20
- If the strings are identical, returns None.
21
- """
22
- # Iterate through both strings up to the length of the shorter string
23
- for i in range(min(len(str1), len(str2))):
24
- if str1[i] != str2[i]:
25
- return i, str1[i], str2[i] # Return the index and differing characters
26
-
27
- # If the strings differ in length
28
- if len(str1) != len(str2):
29
- return min(len(str1), len(str2)), None, None
30
-
31
- # If the strings are identical
32
- return None
33
-
34
- @staticmethod
35
- def find_all_subclasses(cls):
36
- return set(cls.__subclasses__()).union(
37
- [s for c in cls.__subclasses__() for s in Utils.find_all_subclasses(c)])
38
-
39
- @staticmethod
40
- def swap_chars(string, ch1, ch2):
41
- if ch1 == ch2: return string
42
- str_list = []
43
- for char in string:
44
- if char == ch1:
45
- str_list.append(ch2)
46
- elif char == ch2:
47
- str_list.append(ch1)
48
- else:
49
- str_list.append(char)
50
- return ''.join(str_list)
51
-
File without changes