An enigma machine in Python that will take text input in the console or from a file and output to console, file, or email! Interchangeable wheels and salted cyphertext! The user can define the number of character for salt and email password can be stored encryped!

And a simpler verion in Javascript. https://hull1.com/enigma

	# -*- coding: utf-8 -*-
"""
Created on Thu Apr 30 16:39:02 2020
Enigma Machine
@author: HULLB
"""
import random
import re
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
############################ USER INPUT #######################################
userWheel = input('Select 3 of 5 Rotars [123]: ')
if userWheel:
	while len(userWheel) != 3 or re.search('[a-zA-Z]', userWheel) != None:
		print('Invalid Input')
		userWheel = input('Select 3 of 5 Rotars [123]: ')

userPos = input('Select 3 Rotar Positions [ABC]: ')
if userPos:
	while len(userPos) != 3 or re.search('[0-9]', userPos) != None:
		print('Invalid Input')
		userPos = input('Select 3 Rotar Positions [ABC]: ')
		
userSalt = input('Select Salt Level [8]: ')
if userSalt:
	while re.search('[a-zA-Z]', userSalt) != None or int(userSalt) < 0:
		print('Invalid Input')
		userSalt = input('Select Salt Level [8]: ')

readFile = 'n'
userMessage = input('Enter plantext message: ')
if userMessage == 'file':
	readFile = 'y'
	fileName = input('Enter plantext file [text.txt]: ')

sendEmail = 'n'
sendEmail = input('Email cyphertext message? [n]: ')
if sendEmail == 'y':
	emailAddress = input('Send To Address? : ')
	
############################ USER INPUT #######################################
############################ ENTER TEXT #######################################
#Default messages can be enabled for debug
#message = 'abcdefghijklmnopqrstuvwxyz'
#message = 'Call me Fishmeal.'
############################ ENTER TEXT #######################################
############################ DEFAULT SETTINGS #################################
wheelPos1 = 'a'

wheelPos2 = 'b'

wheelPos3 = 'c'

saltValue = 8
############################ DEFAULT SETTINGS #################################
############################ SET VARIABLES ####################################
keyboard = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '.', '!', '?', ',', ':', ';']
avalibleWheel1 = ['k', 'd', 'o', 'g', 'w', 'e', 'q', 'z', 'v', 'u', 'h', 'r', 'n', 'p', 'y', 'c', 'a', 't', 'j', 'b', 'i', 'm', 'x', 's', 'l', 'f', 'K', 'H', 'C', 'N', 'A', 'S', 'L', 'X', 'W', 'V', 'O', 'E', 'M', 'P', 'R', 'G', 'F', 'J', 'B', 'Y', 'I', 'T', 'D', 'U', 'Z', 'Q', '8', '2', '0', '7', '3', '5', '9', '1', '6', '4', ' ', '.', '!', '?', ',', ':', ';']
avalibleWheel2 = ['k', 'g', 'u', 'p', 'e', 'w', 'a', 'f', 'd', 'b', 'v', 'q', 'c', 'l', 'j', 'z', 'h', 'n', 'm', 'o', 'i', 'y', 's', 't', 'r', 'x', 'W', 'P', 'Z', 'K', 'G', 'Q', 'I', 'E', 'M', 'T', 'R', 'V', 'X', 'N', 'U', 'H', 'S', 'L', 'J', 'A', 'Y', 'C', 'B', 'D', 'F', 'O', '7', '9', '6', '8', '0', '5', '2', '1', '3', '4', ' ', '.', '!', '?', ',', ':', ';']
avalibleWheel3 = ['t', 'e', 'c', 'w', 'g', 'x', 'n', 'a', 'l', 'r', 'y', 'h', 'z', 'f', 'u', 'v', 'b', 'o', 'm', 'q', 's', 'j', 'k', 'd', 'i', 'p', 'R', 'X', 'C', 'V', 'A', 'Q', 'T', 'N', 'L', 'F', 'K', 'E', 'D', 'W', 'H', 'S', 'Z', 'G', 'Y', 'J', 'B', 'O', 'P', 'U', 'I', 'M', '9', '2', '5', '8', '3', '0', '6', '4', '7', '1', ' ', '.', '!', '?', ',', ':', ';']
avalibleWheel4 = ['j', 'n', 'u', 'v', 'e', 'g', 'a', 'p', 'm', 'y', 's', 'f', 'i', 'd', 'h', 'o', 'b', 'w', 'x', 'q', 't', 'r', 'l', 'z', 'k', 'c', 'L', 'I', 'Y', 'S', 'A', 'R', 'D', 'P', 'F', 'Z', 'H', 'K', 'W', 'V', 'U', 'Q', 'N', 'O', 'J', 'B', 'X', 'M', 'C', 'T', 'G', 'E', '9', '0', '6', '1', '4', '2', '5', '8', '3', '7', ' ', '.', '!', '?', ',', ':', ';']
avalibleWheel5 = ['p', 'h', 'u', 'f', 'y', 'n', 'r', 'a', 'v', 'c', 'z', 'm', 'q', 'o', 'w', 'l', 'x', 'g', 'j', 'b', 'i', 'k', 't', 'e', 'd', 's', 'N', 'U', 'I', 'T', 'A', 'F', 'G', 'B', 'Z', 'X', 'D', 'Q', 'R', 'J', 'V', 'O', 'L', 'W', 'M', 'C', 'K', 'P', 'H', 'Y', 'S', 'E', '6', '0', '3', '5', '8', '2', '1', '4', '7', '9', ' ', '.', '!', '?', ',', ':', ';']
counter = 10
############################ SET VARIABLES ####################################
############################ DEFAULT SETTINGS #################################
wheel1 = avalibleWheel1

wheel2 = avalibleWheel2

wheel3 = avalibleWheel3
############################ DEFAULT SETTINGS #################################
######################### SET VARIABLES FROM INPUT ############################
if userWheel:
	if userWheel[0] == '1':
		wheel1 = avalibleWheel1
	elif userWheel[0] == '2':
		wheel1 = avalibleWheel2
	elif userWheel[0] == '3':
		wheel1 = avalibleWheel3
	elif userWheel[0] == '4':
		wheel1 = avalibleWheel4
	elif userWheel[0] == '5':
		wheel1 = avalibleWheel5
		
	if userWheel[1] == '1':
		wheel2 = avalibleWheel1
	elif userWheel[1] == '2':
		wheel2 = avalibleWheel2
	elif userWheel[1] == '3':
		wheel2 = avalibleWheel3
	elif userWheel[1] == '4':
		wheel2 = avalibleWheel4
	elif userWheel[1] == '5':
		wheel2 = avalibleWheel5
		
	if userWheel[2] == '1':
		wheel3 = avalibleWheel1
	elif userWheel[2] == '2':
		wheel3 = avalibleWheel2
	elif userWheel[2] == '3':
		wheel3 = avalibleWheel3
	elif userWheel[2] == '4':
		wheel3 = avalibleWheel4
	elif userWheel[2] == '5':
		wheel3 = avalibleWheel5
		
if userPos:
	userPos = userPos.lower()
	wheelPos1 = userPos[0]
	wheelPos2 = userPos[1]
	wheelPos3 = userPos[2]

if userSalt:
	saltValue = int(userSalt)
	if saltValue % 2 != 0:
		saltValue = saltValue +1

if userMessage:
	if readFile == 'y':
		if not fileName:
			fileName = 'text.txt'
		f = open(fileName, 'r')
		message = f.readlines()
		f.close()
		message = ''.join(message)
		#message = message.lower()
	if readFile != 'y':
		#userMessage = userMessage.lower()
		message = userMessage
######################### SET VARIABLES FROM INPUT ############################
############################ DEFINE FUNCTIONS #################################

def emailer(emailAddress):
	MY_ADDRESS = 'ENTER EMAIL ADDRESS'
	ENCRYPTED_PASSWORD = 'ENTER ENCYPTED PASSWORD'
	SMTP_HOST = 'smtp.gmail.com'
	SMTP_PORT = '587'
	names = ['you']
	emails = [emailAddress]    
	
	unsaltedENCRYPTED_PASSWORD = desalinate(8, ENCRYPTED_PASSWORD)
	PASSWORD = startDencryt(unsaltedENCRYPTED_PASSWORD, avalibleWheel1, avalibleWheel2, avalibleWheel3, 'a', 'b', 'c')
	
	with open('cyphertext.txt', 'r', encoding='utf-8') as emailMessage:
		emailMessageContent = emailMessage.read()
		
	#set up the SMTP server
	s = smtplib.SMTP(host=SMTP_HOST, port=SMTP_PORT)
	s.starttls()
	s.login(MY_ADDRESS, PASSWORD)
	
	# For each contact, send the email:
	for name, email in zip(names, emails):
		msg = MIMEMultipart()       # create a message

		# setup the parameters of the message
		msg['From']=MY_ADDRESS
		msg['To']=email
		msg['Subject']="Encrypted Message"
		
		# add in the message body
		msg.attach(MIMEText(emailMessageContent, 'plain'))
		
		# send the message via the server set up earlier.
		s.send_message(msg)
		del msg
		
		PASSWORD = ENCRYPTED_PASSWORD
		del PASSWORD
		
	# Terminate the SMTP session and close the connection
	s.quit()
	
def encrypt(message, wheel, wheelPosition):
	#Set wheel position
	wheelStart = wheel.index(wheelPosition)
	for turns in range (wheelStart):
		wheel.append(wheel.pop(0))
		
	#Split str to array
	#message = message.lower()
	messageArray = [char for char in message]
	
	#Change character array to number array
	messageArrayNum = []
	for i in range (len(messageArray)):
		messageArrayNum.append(keyboard.index(messageArray[i]))
	
	#Change messageArrayNum based on wheel
	cypherMessage = []
	for i in range (len(messageArrayNum)):
		cypherMessage.append(wheel[messageArrayNum[i]])
		#turn the wheel!
		wheel.append(wheel.pop(0))

	#Reassemble array to str
	cypherMessage = ''.join(cypherMessage)
	return(cypherMessage)
	
def decrypt(cypherMessage, wheel, wheelPosition):
	#Set wheel position
	wheelStart = wheel.index(wheelPosition)
	for turns in range (wheelStart):
		wheel.append(wheel.pop(0))

	#Split str to array
	#cypherMessage = cypherMessage.lower()
	cypherMessageArray = [char for char in cypherMessage]
	
	#Change character array to number array
	cypherMessageArrayNum = []
	for i in range (len(cypherMessageArray)):
		cypherMessageArrayNum.append(wheel.index(cypherMessageArray[i]))
		#turn the wheel!
		wheel.append(wheel.pop(0))

	#Change messageArrayNum based on wheel
	message = []
	for i in range (len(cypherMessageArrayNum)):
		message.append(keyboard[cypherMessageArrayNum[i]])
	
	#Reassemble array to str
	message = ''.join(message)
	return(message)

def easySalt(_saltValue, text):
	if _saltValue == 0:
		saltedtext = text
		return(saltedtext)
	else: 
		if _saltValue % 2 != 0:
			_saltValue = _saltValue + 1
		salt=[]
		for i in range(_saltValue):
			salt.append(keyboard[random.randint(0, len(keyboard)-1)])
		salt = ''.join(salt) 
		saltedtext = salt + text
		
		salt=[]
		for i in range(_saltValue):
			salt.append(keyboard[random.randint(0, len(keyboard)-1)])
		salt = ''.join(salt)
		saltedtext = saltedtext + salt
		return(saltedtext)
		
def easyUnsalt(saltyValue, text):
		if saltyValue == 0:
			saltedtext = text
			return(saltedtext)
		else: 
			unsaltedtext = text[saltyValue:]
			unsaltedtext = unsaltedtext[:-saltyValue]
			return(unsaltedtext)

def salt(_saltValue, text):
	if _saltValue == 0:
		saltedtext = text
		return(saltedtext)
	else: 
		if _saltValue % 2 != 0:
			_saltValue = _saltValue + 1
		salt = []
		if len(text) % 2 != 0:
			text = text + ' '
			modtext = True
		else:
			modtext = False
		for i in range (_saltValue):
			salt.append(keyboard[random.randint(0, len(keyboard)-1)])
		salt = ''.join(salt) 
		saltPlace = len(text) // 2
		saltedText = text[:saltPlace] + salt + text[saltPlace:]
		if modtext == True:
			saltedText = saltedText[:-1]
		return saltedText

def unsalt(_saltValue, saltedText):
		if _saltValue == 0:
			unsaltedtext = saltedText
			return(unsaltedtext)
		else: 
			if _saltValue % 2 != 0:
				_saltValue = _saltValue + 1
			saltPlace = len(saltedText) // 2
			saltHalf = _saltValue //2
			unsaltedText1 = saltedText[:-saltPlace - saltHalf]
			unsaltedText2 = saltedText[saltHalf - saltPlace:]
			return unsaltedText1 + unsaltedText2

def startEncryt(message, wheel_1, wheel_2, wheel_3, wheelPos_1, wheelPos_2, wheelPos_3):            
	arrayOfCypherMessages = []
	arrayOfCypherMessages.append(message)
	for i in range (0, counter):
		if i % 2 == 0:
			arrayOfCypherMessages.append(encrypt(arrayOfCypherMessages[i], wheel_1, wheelPos_1))
			
		elif i % 3 == 0:
			arrayOfCypherMessages.append(encrypt(arrayOfCypherMessages[i], wheel_2, wheelPos_2))
			
		else:
			arrayOfCypherMessages.append(encrypt(arrayOfCypherMessages[i], wheel_3, wheelPos_3))
			
	saltedCypherMessage = easySalt(saltValue, arrayOfCypherMessages[counter])
	saltedCypherMessage = salt(saltValue, saltedCypherMessage)
	return(saltedCypherMessage)

def desalinate(salt_Value, saltedCypherMessage):
	unsaltedCypherMessage = easyUnsalt(salt_Value, saltedCypherMessage)
	unsaltedCypherMessage = unsalt(salt_Value, unsaltedCypherMessage)
	return unsaltedCypherMessage

def startDencryt(unsaltedCypherMessage, wheel_1, wheel_2, wheel_3, wheelPos_1, wheelPos_2, wheelPos_3):  
	arrayOfDecypheredMessages = []
	arrayOfDecypheredMessages.append(unsaltedCypherMessage)
	for i in range (0, counter):
		if i % 2 != 0:
			arrayOfDecypheredMessages.append(decrypt(arrayOfDecypheredMessages[i], wheel_1, wheelPos_1))
			
		elif i % 3 == 0:
			arrayOfDecypheredMessages.append(decrypt(arrayOfDecypheredMessages[i], wheel_2, wheelPos_2))
			
		else:
			arrayOfDecypheredMessages.append(decrypt(arrayOfDecypheredMessages[i], wheel_3, wheelPos_3)) 
	return arrayOfDecypheredMessages[counter]
############################ DEFINE FUNCTIONS #################################
############################### START #########################################   
print(message)
print('\n')
saltedCypherMessage = startEncryt(message, wheel1, wheel2, wheel3, wheelPos1, wheelPos2, wheelPos3)
print(saltedCypherMessage)
"""
#Uncomment to test decrypt without running seperate script
print('\n')
unsaltedCypherMessage = desalinate(saltValue, saltedCypherMessage)
print(unsaltedCypherMessage)
print('\n')
decypheredMessage = startDencryt(unsaltedCypherMessage, wheel1, wheel2, wheel3, wheelPos1, wheelPos2, wheelPos3)
print(decypheredMessage)
"""
print('\n')
print ('Output written to cyphertext.txt')
print(saltedCypherMessage,  file=open('cyphertext.txt', 'w'))

if sendEmail == 'y':    
	emailer(emailAddress)
	print('\n')
	print ('Output send to ', emailAddress)
################################ END ##########################################

As it turns out it’s also useful the have some Python code that will generate new wheels for the enigma machine.

# -*- coding: utf-8 -*-
"""
Created on Sun May  3 15:29:44 2020
Enigma Machine Wheel Generator
@author: HULLB
"""
import random

def wheelGen():
	wheel = []
	keyboard = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '.', '!', '?', ',', ';', ':', '(', ')', '$', '*', '[', ']', '&', '-', '=', '@', '"', '%', '~', '|', '/', '_']
	for i in range (0, len(keyboard)):
		num = random.randint(0, len(keyboard)-1)
		char = keyboard[num]
		wheel.append(char)
		del keyboard[num]
	#wheel.append(' ')
	print(wheel)
	
for i in range(0,5):
	wheelGen()

Further Reading:
https://en.wikipedia.org/wiki/Enigma_machine

https://hull1.com/python/2020/08/15/python-enigma.html