TDD-Deciphered.com

Part 10: Assembling the Machine

01/02/2010

There are still two electrical elements of the system we've not simulated; the reflector and the plugboard. Both of these, as mentioned above, will implement Enigma_Encryptor_Interface.

The reflector is extremely simple, and we can implement it in a simpler version of the technique we used for the wheels:

Enigma_Reflector_Abstract sets up the basics and implements the required getOutputCharacterForInputCharacter function, and the concrete implementations Enigma_Reflector_B and Enigma_Reflector_C will include the mapping data, which we can again find at http://www.codesandciphers.org.uk/enigma/rotorspec.htm

We'll go back to writing tests first, with the folloiwing in a new ReflectorTest.php file:
  1. <?php
  2.  
  3. require_once ('../../library/initialise.php');
  4.  
  5. class Enigma_ReflectorTest extends PHPUnit_Framework_TestCase
  6. {
  7. /**
  8.   *
  9.   * @param $rotor Enigma_Reflector_Abstract
  10.   * @param $in
  11.   * @param $out
  12.   * @dataProvider rotorMappingsDataProvider
  13.   */
  14. function testVariousMappings(Enigma_Reflector_Abstract $rotor, $in,$out)
  15. {
  16. $this->assertSame($out,$rotor->getOutputCharacterForInputCharacter($in));
  17. }
  18.  
  19. function rotorMappingsDataProvider()
  20. {
  21. return array(
  22. array(new Enigma_Reflector_B, 'B', 'R'),
  23. array(new Enigma_Reflector_B, 'O', 'M'),
  24. array(new Enigma_Reflector_C, 'M', 'X'),
  25. array(new Enigma_Reflector_C, 'R', 'K'),
  26. );
  27. }
  28.  
  29. }
and quickly build Enigma_Reflector_Abstract:
  1. <?php
  2. abstract class Enigma_Reflector_Abstract implements Enigma_Encryptor_Interface
  3. {
  4. protected $_halfMapping=array();
  5. protected $_fullMapping=null;
  6.  
  7. public function __construct()
  8. {
  9. if(is_null($this->_fullMapping)) {
  10. $this->_fullMapping=$this->_halfMapping+array_flip($this->_halfMapping);
  11. }
  12. }
  13.  
  14. public function getOutputCharacterForInputCharacter ($inputCharacter) {
  15. $inputCharacter=strtoupper($inputCharacter);
  16. if (isset($this->_fullMapping[$inputCharacter])) {
  17. return ($this->_fullMapping[$inputCharacter]);
  18. } else {
  19. throw new Exception('Invalid reflector input');
  20. }
  21. }
  22. }
and the B and C classes
  1. <?php
  2. class Enigma_Reflector_B extends Enigma_Reflector_Abstract
  3. {
  4. protected $_halfMapping=array('A'=>'Y', 'B'=>'R', 'C'=>'U', 'D'=>'H', 'E'=>'Q', 'F'=>'S',
  5. 'G'=>'L', 'I'=>'P', 'J'=>'X', 'K'=>'N', 'M'=>'O', 'T'=>'Z', 'V'=>'W');
  6. }
  1. <?php
  2. class Enigma_Reflector_C extends Enigma_Reflector_Abstract
  3. {
  4. protected $_halfMapping=array('A'=>'F', 'B'=>'V', 'C'=>'P', 'D'=>'J', 'E'=>'I', 'G'=>'O',
  5. 'H'=>'Y', 'K'=>'R', 'L'=>'Z', 'M'=>'X', 'N'=>'W', 'T'=>'Q', 'S'=>'U',
  6. );
  7. }
Again, as the tests run, we can trust our mapping.


Now we can start thinking about how these elements can be put together to form a complete encoding device. This device will be 3 rotors, 1 reflector, no plugboard.

We need to build 3 slots, and put a configured rotor in each. We set each slot to observe the slot to the right to handle the mechanical phase. We add a reflector.

When we press each button:

We trigger the mechanical phase by calling the rightmost slot's incrementRotorOffset() function. This then causes any required rollovers.

In the electrical phase, we take an input character and pass it to the input of the first rotor slot, which then routes it via the rotor to its output.

We take that output signal and feed it through the middle slot.

We take that output signal and feed it through the rightmost slot.

We take that output signal and feed it through the reflector.

We then feed the signal backwards through the rotors in turn.

Ah - problem - we've not specified a way to send signals backwards through an asymmetric encryptor like the rotor wheel. In fact we've not really considered the fact that the rotors (and therefore the slots) are asymmetric.

We can solve this quite simply with a new function of getOutputCharacterForInputCharacterReverse, which we'll enforce in the slots and rotors as a new interface: Enigma_Encryptor_Asymmetric_Interface:
  1. <?php
  2. interface Enigma_Encryptor_Asymmetric_Interface extends Enigma_Encryptor_Interface
  3. {
  4. public function getOutputCharacterForInputCharacterReverse ($inputCharacter);
  5. }
and we can replace Enigma_Encryptor_Interface with the new extended interface in Enigma_Rotor_Abstract and Enigma_RotorSlot.

If we run our tests now, we'll get complaints about the missing functions, so we add these:

In Enigma_RotorSlot:
  1. public function getOutputCharacterForInputCharacterReverse ($inputCharacter)
  2. {
  3. $inputCharacter = strtoupper($inputCharacter);
  4.  
  5.  
  6. $rotorInputCharacter = $this->_getCharacterOffsetBy($inputCharacter, $this->_rotorOffset-1);
  7.  
  8. $rotorOutputCharacter = $this->_rotor->getOutputCharacterForInputCharacterReverse(
  9. $rotorInputCharacter
  10. );
  11.  
  12. $outputCharacter = $this->_getCharacterOffsetBy(
  13. $rotorOutputCharacter, 0 - ($this->_rotorOffset -1)
  14. );
  15.  
  16. return ($outputCharacter);
  17. }
and in Enigma_Rotor_Abstract:
  1. public function getOutputCharacterForInputCharacterReverse ($inputCharacter)
  2. {
  3. $inputCharacter = strtoupper($inputCharacter);
  4. $coreInputCharacter = $this->_getCharacterOffsetBy($inputCharacter, $this->_ringOffset - 1);
  5.  
  6. $flipMap=array_flip($this->_coreMapping);
  7.  
  8. $coreOutputCharacter = $flipMap[$coreInputCharacter];
  9. $outputCharacter = $this->_getCharacterOffsetBy(
  10. $coreOutputCharacter, 0 - ($this->_ringOffset -1)
  11. );
  12.  
  13. return ($outputCharacter);
  14. }
Note that all that's happening inside here is that we're flipping the rotor's mapping array when we route the signal through it.

Again, our tests run cleanly; check in (rev 11)!

Now we have all the parts we need to build an Enigma machine (remembering that not all types of Enigma had plugboards, and those that have them could ignore them if required). As this is a character encoding device, we can re-use our interface, giving us:

class Enigma_Machine implements Enigma_Encryptor_Interface

To set up the machine, we need to specify and add its various components. Again, for the sake of speed, I'm not going to test all the setters and getters, but we have:
  1. /**
  2.   * Number of slots this machine has. Default to original three
  3.   *
  4.   * @var int
  5.   */
  6. protected $_numSlots=3;
  7.  
  8. /**
  9.   * Set of slots to house rotors
  10.   *
  11.   * @var array of Enigma_RotorSlot
  12.   */
  13. protected $_slots=array();
  14.  
  15. /**
  16.   * Space for reflector
  17.   *
  18.   * @var Enigma_Reflector_Abstract
  19.   */
  20. protected $_reflector;
  21.  
  22. /**
  23.   * Does this machine have a steckboard? Some don't
  24.   *
  25.   * @var boolean
  26.   */
  27. protected $_hasSteckerBoard=false;
  28.  
  29. /**
  30.   * Space for the steckerboard if we add one
  31.   *
  32.   * @var Enigma_SteckerBoard
  33.   */
  34. protected $_steckerBoard;
We can now assemble the machine in its constructor:
  1. public function __construct()
  2. {
  3. if($this->_hasSteckerBoard)
  4. { // Can implement this later!
  5. $this->_steckerBoard=new Enigma_SteckerBoard;
  6. }
  7.  
  8. for($i=0; $i<$this->_numSlots;$i++) {
  9. $this->_slots[$i]=new Enigma_RotorSlot;
  10. if($i>0) { //higher slots watch lower ones
  11. $this->_slots[$i-1]->registerTurnoverObserver($this->_slots[$i]);
  12. }
  13. }
  14. }
and build the configuration methods:
  1. public function setRotors($rotors)
  2. {
  3. if(!(is_array($rotors) && ($this->_numSlots==count($rotors))))
  4. {
  5. throw new Exception("Rotors must be $this->_numSlots-element array");
  6. }
  7.  
  8. $i=0;
  9. foreach($rotors as $rotor)
  10. {
  11. if(!$rotor instanceof Enigma_Rotor_Abstract) {
  12. throw new Exception('Slots can only be loaded with Enigma_Rotor_* class');
  13. }
  14.  
  15. $this->_slots[$i]->loadRotor($rotor);
  16. $i++;
  17. }
  18. }
  19.  
  20. public function setRotorStartPositions($positions)
  21. {
  22. if(!(is_array($positions) && ($this->_numSlots==count($positions))))
  23. {
  24. throw new Exception("Rotor positions must be $this->_numSlots-element array");
  25. }
  26.  
  27. $i=0;
  28. foreach($positions as $position)
  29. {
  30. $this->_slots[$i]->setRotorOffset($position);
  31. $i++;
  32. }
  33. }
  34.  
  35. public function setReflector(Enigma_Reflector_Abstract $reflector)
  36. {
  37. $this->_reflector=$reflector;
  38. }
  39.  
  40.  
  41. public function getOutputCharacterForInputCharacter ($inputCharacter)
  42. {
  43. if(!$this->_isFullyBuilt()) {
  44. throw new Exception('Device not fully built');
  45. }
  46.  
  47. //We'll trigger the mechanical phase here as we can't send a signal without doing so
  48. //(ok, strictly there's a hardware hack)
  49. $this->_slots[0]->incrementRotorOffset();
  50.  
  51. $char=$inputCharacter;
  52.  
  53. //line up our coding stages and feed the signal through them:
  54.  
  55. if($this->_hasSteckerBoard) {
  56. $char=$this->_steckerBoard->getOutputCharacterForInputCharacter($char);
  57. }
  58.  
  59. //rotors inbound
  60. foreach($this->_slots as $slot)
  61. {
  62. $char=$slot->getOutputCharacterForInputCharacter($char);
  63. }
  64.  
  65. // through the reflector:
  66. $char=$this->_reflector->getOutputCharacterForInputCharacter($char);
  67.  
  68. //rotors outbound (current flows other way)
  69. for($i=$this->_numSlots-1;$i>=0;$i--)
  70. {
  71. $slot=$this->_slots[$i];
  72. $char=$slot->getOutputCharacterForInputCharacterReverse($char);
  73. }
  74.  
  75. //steckers are symmetrical
  76. if($this->_hasSteckerBoard) {
  77. $char=$this->_steckerBoard->getOutputCharacterForInputCharacter($char);
  78. }
  79.  
  80. return($char);
  81.  
  82. }
  83.  
  84. /**
  85.   *
  86.   * @return Enigma_SteckerBoard
  87.   */
  88. public function getSteckerBoard()
  89. {
  90. return($this->_steckerBoard);
  91. }
  92.  
  93. protected function _isFullyBuilt()
  94. {
  95. //for now, lie.
  96. return true;
  97. }
Now, we have enough code to directly simulate a (basic) Engima encoder – but how can we use and test it?

Ideally, we'd test some input data and some known conditions against some known output data. The question is – do we have any such data?

Unfortunately not. It's extremely hard to find sample encrypts online, especially messages that have been encrypted with no plugboard, so we have no direct comparison data.

One test we can do is to check the device's symmetrical properties. If we encipher a message with known settings, then take that output and put it back through the same device with the same start settings, then we should recover our original message.

We'll do this with a test rig first rather than a unit test, as there's a lot of setup to do that we've not yet fully implemented; as mentioned above, this would be the duty of our 'Enigma_Signaller' class.

Our script (in cli/testRig.php) looks like:
  1. <?php
  2. require_once ('../library/initialise.php');
  3.  
  4. $messageString='Action This Day';
  5.  
  6. $messageArray=preg_split('//',$messageString);
  7.  
  8. $messageArrayClean=array();
  9. foreach($messageArray as $char) {
  10. if(preg_match('/[a-z]/i',$char)) {
  11. $messageArrayClean[]=strtoupper($char);
  12. }
  13. }
  14.  
  15. $device=new Enigma_Machine();
  16.  
  17. $rotors=array();
  18. $rotors[0]=new Enigma_Rotor_I;
  19. $rotors[0]->setRingOffset('C');
  20. $rotors[1]=new Enigma_Rotor_II;
  21. $rotors[1]->setRingOffset('A');
  22. $rotors[2]=new Enigma_Rotor_III;
  23. $rotors[2]->setRingOffset('A');
  24.  
  25. $device->setRotors($rotors);
  26. $device->setRotorStartPositions(array('P','A','A'));
  27.  
  28. $reflector=new Enigma_Reflector_B;
  29.  
  30. $device->setReflector($reflector);
  31.  
  32. echo ("\n".join('',$messageArrayClean)."\n\n");
  33.  
  34. foreach ($messageArrayClean as $char)
  35. {
  36. $char=$device->getOutputCharacterForInputCharacter($char);
  37.  
  38. echo $char;
  39.  
  40. }
  41. echo ("\n\n");
One caveat to add here is that our rotors are numbered right-to-left, because that's the order of incoming signal and turnover. Therefore a key setting of 'AAP' (as it might be written in a key sheet or discussion of the machine) is applied as $device->setRotorStartPositions(array('P','A','A'));

The script also gives us the chance to discuss how the machine was used. We won't try and apply any of the German security practices yet, but we'll take a simple, short message, and encode it.

Our test message is 'Action This Day'. This was the phrase with which Churchill ordered that Bletchley Park was to have "all necessary support" [CHECK QUOTE] after being informed by its departmental heads in a very irregular letter that it was starved of resources. As such it reflects both the recognition of Bletchley's importance and the point at which it began to realise its full potential.

To encode this message, we have to convert it to a format which the Enigma can accept. This means upper-case letters only; no spaces, no punctuation. Plus, to get it into our machine's input, we'll need to split it into individual characters.

Spaces were often replaced with X's in Enigma messages – a procedure which turned out to be a weakness in the security policies, as it meant that 'X" was the most-used character in such messages, and made word breaks easier to find in decoded messages. For now, though, we'll just skip the spaces.

So, by 'standardising' the message 'Action This Day', we get an array of A,C,T,I,O,N,T,H,I,S,D,A,Y.

Once we've set our machine up as specified above, we run the command-line script:

$ php -q testRigWithDevice.php

ACTIONTHISDAY

DWOUFBIGODOGS

Now, I'm not sure where the canine fixation of this message happened to come from, but if we re-run this script with the encrypted form as our input, we'll see whether we're barking up the wrong tree. Edit the line

$messageString='Action This Day';

to

$messageString='DWOUFBIGODOGS';

and with luck we'll get our input back!

$ php -q testRigWithDevice.php

DWOUFBIGODOGS

ACTIONTHISDAY

It works! However, we still need to check it against a "real" message. If we encode the above message on a 'real' device, will we get the same results?

Well, sadly, I don't own a real Enigma machine (much as I'd love to) so we'll have to make do with a reference implementation. I used Terry Long's Enigma Simulator for OSX from http://www.terrylong.org/ to verify this message and it does indeed match the data above. We have a working machine! (so check in - r12!)

Next time, we'll add the final piece required to simulate most of the basic Enigma types - the Steckerboard - and look at deciphering some real wartime messages.

All content copyright Richard George (richard@phase.org), 2009-2010

Sponsored links to recommended books: