Date de publication : Sep 19, 2015 12:33:20 AM
Pocket Enigma Cipher Machine The Pocket Enigma Cipher Machine is a superbly designed toy that demonstrates some of the principles of a real Enigma Cipher Machine, as pictured on the cover of this issue. The Enigma Cipher Machine was used during World War two by the German armed forces, to send secrete messages to submarines and solders. I obtained my Pocket version from Bletchley Park [ http://www.bletchleypark.org.uk/ ], but unfortunately it no longer appears to be on sale. It is made only of plastic and cardboard and is substantially simpler when compared with a real Enigma cipher machine! On the other hand, if you enjoy encryption and ciphers you will get a kick out of the Pocket Enigma. It is not too difficult to understand either. The Pocket Enigma Cipher Machine is not even close to an unbreakable cipher – it is a trivial cipher to break but it is fun. Therefore, do not use it to encrypt sensitive information. A full review of the Pocket Enigma Machine, including a detailed description and further reading, can be found at: http://www.savory.de/pocket_enigma.htm How does it work? Each plaintext character is replaced with another character called the cipher text character. The cipher text character is chosen according to the connection between characters printed on the wheel, where there are two wheels to choose from. 39 In more detail, the algorithm follows: 1 . Cipher wheel (1 or 2) is chosen. 2. The key character is chosen. 3. The start character is chosen. 4. The wheel is set to the key character and the start character is encoded. 5. The wheel is moved to the start character and the first message character is encoded. 6. The wheel is incremented by 1 position, and the next message character is encoded. 7. Repeat step 6 unti l the entire message is encoded. 8. The encoded message is arranged such that the encoded start character is separated from rest of the encoded message, which is arranged in blocks of, typically, five characters. For the message to be successfully decoded by the recipient, they must already know the wheel number and key character that was used to encrypt the message. Now for the limitations: 1 . Only upper case characters can be encoded. 2. No punctuation can be encoded, apart from full-stops which are traditionally substituted with X. With a bit of imagination the encoding algorithm can easi ly be modified. For example, more wheels could be used, or the increment could be varied or even reversed. Python Pocket Enigma Cipher Machine Use a text editor such as nano or emacs to create a new Python file called Py- Eni gma. py. Then add: #! /usr/bi n/python VERSION = " v1. 0. 0" BUILDDATE = " 01/06/2014" # Gl obal vari abl es wi th starti ng val ues sel ectedWheel = 1 poi nterChar = ' D' poi nterInt = ord(poi nterChar)-65 codeStartChar = ' J ' codeStartInt = ord(codeStartChar)-65 i ncrement = 1 bl ocksi ze = 5 to the top of the file. In this article each piece of the program is numbered and should be appended to this file, to create the final program. The ord() function returns the numerical value of an ASCII character. ord(' A' ) returns 65, whereas ord(' B' ) returns 66 etc.. Lower case characters have different numerical values, but since the cipher only has one set of characters, upper case characters are used throughout the program. The sel ectedWheel variable is used to select which wheel is used, poi nterChar is the initial wheel settings and codeStartChar is the starting character. Integer values of these variables are also stored to simplify manipulating the wheels within the functions that follow. The i ncrement is the amount that a wheel is turned after each character is encrypted and bl ocksi ze is used to split the encrypted string into pieces that are separated by spaces. 1. Analysis of the wheels The wheels have no characters on them, just a lot of connections. One position has an arrow, or pointer, and is Py-Enigma.py (1 /8) 40 taken as the starting position (actually position 0). Looking at the pictures on the first page of this article, it is clear that the connections simply connect from one position to another. These connections indicate how one character should be substituted for another. The wheels can be summarised using two Python lists: # Wheel defi ni ti ons wheel 1 = [- 1, 3, - 5, 7, - 3, 2, 3, - 2, 4, - 3, -7, 6, -4, 1, - 1, 6, 3, -6, 2, - 3, - 2, -6, 2, 5, - 2, 1] wheel 2 = [2, 2, - 2, - 2, - 8, 3, 9, 5, - 3, 1, - 1, 2, - 5, - 2, 2, - 9, - 2, 8, 2, 2, - 2, - 2, 8, 1, - 1, - 8] Add these two lists to the end of the Python fi le. Each list has 26 entries, since there are 26 characters around the outside of the wheel. The number in each entry corresponds to the joining line on the wheel, where a negative number implies moving to the left and a positive number implies moving to the right. The Python version of the algorithm relies on the modulo (%) operator to stay within the A--Z character range. First, the character should be converted to an integer. Then the offset should be applied, using the modulo operator. For example, using ' A' and the first wheel: i ntVal ue = ord(' A' ) - 65 # returns 0 i ntVal ue = i ntVal ue + wheel 1[i ntVal ue] # returns - 1 i ntVal ue = i ntVal ue % 26 # returns 25 charVal ue = chr(i ntVal ue + 65) # returns ' Z' If the number is bigger than the 26 character range, then the modulo operator causes the number to become less than 26. This means that adding 1 to the value of ' Z' returns ' A' : i ntVal ue = ord(' Z' ) - 65 # returns 25 i ntVal ue = i ntVal ue + wheel 1[i ntVal ue] # returns 26 i ntVal ue = i ntVal ue % 26 # returns 0 charVal ue = chr(i ntVal ue + 65) # returns ' A' In both of these examples, the chr() function is used to convert an integer value back into the associated ASCII character. It helped me to visualise the modulo maths by imagining that it turned the alphabet into a circle. 2. Encrypting or decrypting a character The Pocket Enigma algorithm states that the wheel should be moved 1 position clockwise after each message character is encoded. This means that a repeated character in the message is not encrypted as the same character. Append the code below to the end of the Python file. # Encrypt or decrypt a si ngl e character def transformChar(character, sel ectedWheel , poi nter): character = character. upper() # Ensure that the character is Upper Case. i f(65 <= ord(character) <= 90): # Onl y characters A- Z can be encrypted or decrypted. char_num = ord(character) - 65 # Convert ASCII to al phabeti cal posi ti on of the character. # Choose the offset for wheel one or two. Then use the poi nter val ue. i f (sel ectedWheel == 1): offset = wheel 1[(char_num - poi nter)%26] # Use mod wi th 26 to stay wi thi n ci rcl e el se: offset = wheel 2[(char_num - poi nter)%26] # Use mod wi th 26 to stay wi thi n ci rcl e # Convert al phabeti cal posi ti on of the character back to ASCII character = chr(65 + (char_num + offset)%26) # Convert posi ti on back to ASCII character el se: character = ' ' # Ensure that nothi ng i s returned i f the character i s not A- Z. return character Py-Enigma.py (2/8) Py-Enigma.py (3/8) 41 This function takes an input character, the selected wheel number and the current pointer position. The pointer represents the position of the wheel and is substracted from the integer value of the character before it is used to find the offset. If a character that is not within the A--Z range is passed into the function, then it is ignored and no character is returned. 3. Encrypting a string To encrypt a string, each character should be passed to the transformChar() function. Append the code below to the Python fi le. # Encrypt a stri ng def encrypt(pl ai ntext): poi nter = poi nterInt # Set the wheel to the key character, using the gl obal vari abl e ci pher = ' ' # Encrypt the Al pha Start character. ci pher += transformChar(codeStartChar, sel ectedWheel , poi nter) ci pher += ' ' poi nter = codeStartInt # Set the wheel to the Al pha Start character. bl ock = 0 # Encrypt each l etter i n the pl ai ntext stri ng for o_char i n pl ai ntext: # Substi tute ' . ' wi th ' X' i f o_char == ' . ' : o_char = ' X' # Encrypt thi s character e_char = transformChar(o_char, sel ectedWheel , poi nter) # Do somethi ng i f the character was encrypted ok. i f l en(e_char) > 0: bl ock += 1 i f bl ock > bl ocksi ze: ci pher += ' ' # Add a space after a bl ock of bl ocksi ze characters. bl ock = 1 # Rememberi ng the character that was bl ocksi ze+1. ci pher += e_char # Add the character to the resul t. poi nter = (poi nter + i ncrement)%26 # Turn the wheel , usi ng mod 26 to return to zero return ci pher The function takes a string and returns an encrypted string. The pointer starts from the initial value (key character) set using the global variable poi nterInt. Then the starting character is encrypted and appended to the encrypted string. The pointer value is reset to the starting character and then each character in the string is encrypted. To retain some punctuation, each ' . ' is replaced with ' X' . The encrypted output is also split into fixed size blocks that are separated by spaces to help further disguise the words. 4. Decrypting a string The connections on the wheels are bi-directional. Therefore, if a character is encoded as ' F' and the wheel is in the same position, encoding ' F' wi ll return the original character. Consequently, the same encryption routine can be used to decrypt a string. Append the program at the top of the next page to the Python file. This function takes an encrypted string and returns a decrypted string. Notice that punctuation and spaces are not recovered during the encryption. Therefore, the person that receives the encrypted message wi ll need to put those back in by hand. Py-Enigma.py (4/8) 42 # Decrypt a stri ng def decrypt(ci pher): poi nter = poi nterInt # Set the wheel to the key character. # Extract and decrypt the Al pha Start character. poi nter = ord(transformChar(ci pher[: 1] , sel ectedWheel , poi nter))-65 pl ai ntext = ' ' # Output stri ng wi th no characters # Decrypt each l etter i n the ci pher. for e_char i n ci pher[1: ] : # Decrypt thi s character o_char = transformChar(e_char, sel ectedWheel , poi nter) # Do somethi ng i f the character was decrypted ok. i f l en(o_char) > 0: pl ai ntext += o_char # Add the character to the resul t. poi nter = (poi nter + i ncrement)%26 # Turn the wheel , usi ng mod 26 to return to zero return pl ai ntext 5. Welcome, menu & input functions To call the encrypt and decrypt functions, a text menu provides a simple interface. Add the code given below to the end of the Python file. # Wel come message def wel come(message): pri nt(message) pri nt(" Versi on, %s, %s" % (VERSION, BUILDDATE)) # Pri nt the avai l abl e menu opti ons def showMenu(mi n, max, qui t): pri nt(" \n" + 30 * ' - ' ) pri nt(" P y - E N I G M A" ) pri nt(" M A I N - M E N U" ) pri nt(30 * ' - ' + " \n" ) for i i n xrange(mi n, max+1): i f i == 1: pri nt(" 1. Set Wheel = %d" % sel ectedWheel ) eli f i == 2: pri nt(" 2. Set Poi nter = %s" % poi nterChar) eli f i == 3: pri nt(" 3. Set Code Start = %s" % codeStartChar) eli f i == 4: pri nt(" 4. Set Increment = %d" % i ncrement) eli f i == 5: pri nt(" 5. Set Bl ock Si ze = %d" % bl ocksi ze) eli f i == 6: pri nt(" 6. Encrypt a Message" ) eli f i == 7: pri nt(" 7. Decrypt a Message" ) eli f i == 8: pri nt(" 8. Nothi ng Yet" ) eli f i == 9: pri nt(" 9. Nothi ng Yet" ) el se: conti nue pri nt(" \n %d. Exi t program\n" % qui t) pri nt(30 * ' - ' ) The first function is used to report the version and bui ld date, whereas second function prints out the menu Py-Enigma.py (5/8) Py-Enigma.py (6/8) 43 choices. To read values that correspond to the menu options three simple input functions are needed. Add the functions below to the end of the Python fi le. def getVal ue(request=" Enter choi ce: " ): whi l e 1: i nputVal ue = raw_i nput(request) # Pri nt request and read the repl y i f l en(i nputVal ue) == 0: # If the stri ng has no l ength, report an error pri nt(" Error: no val ue gi ven" ) conti nue return i nputVal ue # If the stri ng has one or more characters, return the val ue def getInteger(mi n, max, request, checkRange = True): whi l e 1: i nputVal ue = getVal ue(request) # Pri nt request and read repl y try: i ntVal ue = i nt(i nputVal ue) # Try to convert the stri ng to an integer except Val ueError: pri nt(" Error: \" %s\" i s not an i nteger" % i nputVal ue) # If i t i s not an i nteger conti nue i f (i ntVal ue < mi n or i ntVal ue > max) and checkRange: # Check the range i f needed pri nt(" Error: \" %d\" i s outsi de range [%d--%d] " % (i ntVal ue, mi n, max)) conti nue return i ntVal ue # Return the i nteger val ue. def getCharacter(mi n, max, request): whi l e 1: i nputVal ue = getVal ue(request) # Pri nt request and read the repl y i f l en(i nputVal ue) ! = 1: # A character i s a stri ng of l ength 1 pri nt(" Error: \" %s\" i s not a si ngl e character" % i nputVal ue) conti nue charVal ue = i nputVal ue. upper() # Convert the stri ng to upper case i f ord(charVal ue) < ord(mi n) or ord(charVal ue) > ord(max): # Check the range pri nt(" Error: \" %s\" i s outsi de range [%s--%s] " % (charValue, mi n, max)) conti nue return charVal ue # return the character val ue These functions are used to read a value from the keyboard, read an integer value and read a character value, respectively. The functions prevent a string without any characters from being accepted, check the type of the input string and if it is within the allowed range of numbers or characters. 6. Finishing the program To finish the program, a function is needed to call all of the other pieces of the program. Add the main function at the top of the next page to the end of the Python fi le. Then save the fi le and make it executable by typing: chmod 755 Py- Eni gma. py Then run the program by typing: . /Py- Eni gma. py The program wi ll print the menu and allow changes to the settings to be made. If the settings are updated, then the menu is printed again with the new values. The input functions are used to make sure that the settings do not go outside of their allowed range. The menu can also be used to encrypt or decrypt strings, where the result is printed on the screen. With the default settings of wheel 1, key character ' D' and start character ' J ' , the message " Attack at dawn. " becomes " M UQXZI MGAZE DKS" . Decoding this returns " ATTACKATDAWNX" . Py-Enigma.py (7/8) 44 # Mai n functi on def mai n(): gl obal sel ectedWheel , poi nterChar, poi nterInt, codeStartChar, codeStartInt, i ncrement, bl ocksi ze wel come(" Py- Eni gma - The Pocket Eni gma Ci pher Machi ne" ) menuMi n = 1 menuMax = 7 menuQui t = 0 whi l e 1: showMenu(menuMi n, menuMax, menuQui t) # Show the menu # Get the user choi ce, wi thout checki ng the range userChoi ce = getInteger(0, 0, " Enter choi ce [%d--%d] : " % (menuMi n, menuMax), Fal se) # Take acti on as per sel ected menu-opti on. i f userChoi ce == menuQui t: break # Leave the whi l e l oop eli f userChoi ce == 1: sel ectedWheel = getInteger(1, 2, " Enter Codi ng Wheel [1 or 2] : " ) eli f userChoi ce == 2: poi nterChar = getCharacter(' A' , ' Z' , " Enter Poi nter Posi ti on [A to Z] : " ) poi nterInt = ord(poi nterChar)-65 eli f userChoi ce == 3: codeStartChar = getCharacter(' A' , ' Z' , " Enter Codi ng Start Posi ti on [A to Z] : " ) codeStartInt = ord(codeStartChar)-65 eli f userChoi ce == 4: i ncrement = getInteger(- 1, 1, " Enter Increment [- 1, 0 or 1] : " ) eli f userChoi ce == 5: bl ocksi ze = getInteger(1, 10, " Enter Bl ock Si ze [1 to 10] : " ) eli f userChoi ce == 6: pl ai ntext = getVal ue(" Enter Pl ai ntext: " ) pri nt(" Encrypti on: %s => %s" % (pl ai ntext, encrypt(pl ai ntext))) eli f userChoi ce == 7: ci pher = getVal ue(" Enter Ci pher: " ) pri nt(" Pl ai ntext: %s => %s" % (ci pher, decrypt(ci pher))) el se: pri nt(" Error: \" %d\" i s not a vali d choi ce" % userChoi ce) pri nt(" \nGoodbye. \n" ) #Run the program i f i t i s the pri mary modul e i f __name__ == ' __mai n__' : mai n() The mai n() function starts by declaring the global variables as gl obal. This is necessary to prevent Python from creating a local version of the same variable when a value is assigned. It is not needed if the values are only used. The welcome message is printed. Then in the whi l e loop, the menu is printed. The users choice is read and checked against each of the menu options. Depending on the menu option, the required action is taken. The Py- Eni gma. py program is a Python version 2 program that cannot be used with Python3 without modification. The program was tested on the latest Raspbian image. What next? Well that depends... let me know what you think. All/any feedback is appreciated.