--ENIGMA emulator by rnd -- programming ~30 mins, total 3hrs 30 minutes with debugging - cause of youtube video with missing -- key detail - reflector! -- REFERENCES: -- 1. https://en.wikipedia.org/wiki/Enigma_machine -- 2. http://users.telenet.be/d.rijmenants/en/enigmatech.htm#reflector -- 3. https://www.youtube.com/watch?src_vid=V4V2bpZlqx8&v=G2_Q9FoD-oQ -- default settings settings = {} settings.set = function(reflector, plugboard, rotors) settings.reflector = reflector or 2017 settings.plugboard = plugboard or 2017 settings.rotors = {} if rotors then table.insert(settings.rotors, rotors[1]) table.insert(settings.rotors, rotors[2]) table.insert(settings.rotors, rotors[3]) table.insert(settings.rotors, rotors[4]) else table.insert(settings.rotors, 0) table.insert(settings.rotors, 0) table.insert(settings.rotors, 0) table.insert(settings.rotors, 2018) table.insert(settings.rotors, 2017) table.insert(settings.rotors, 2020) end --if not enigma_encrypt then if true then scramble = function(input,password,sgn) -- permutes text randomly, nice after touch to stream cypher to prevent block analysis _G.math.randomseed(password); local n = #input; local permute = {} for i = 1, n do permute[i] = i end --input:sub(i, i) for i = n,2,-1 do local j = math.random(i-1); local tmp = permute[j]; permute[j] = permute[i]; permute[i] = tmp; end local out = {}; if sgn>0 then -- unscramble for i = 1,n do out[permute[i]] = string.sub(input,i,i) end else -- scramble for i = 1,n do out[i] = string.sub(input,permute[i],permute[i]) end end return table.concat(out,"") end local permutation = function(n,password,sgn) -- create random permutation of numbers 1,...,n _G.math.randomseed(password); local permute = {} for i = 1, n do permute[i] = i end for i = n,2,-1 do local j = math.random(i-1); local tmp = permute[j]; permute[j] = permute[i]; permute[i] = tmp; end return permute; end -- produces permutation U such that U^2 = 1; modified fisher-yates shuffle by rnd local reflector = function(n,password) _G.math.randomseed(password) local permute = {} local used = {}; for i = 1, n do permute[i] = i end local rem = n; for i = n,2,-1 do if not used[i] then local j = math.random(rem); -- now we need to find j-th unused idx local k = 1; local l = 0; -- k position, l how many we tried while l < j do if not used[k] then l=l+1; end k=k+1 end j=k-1; used[i]=true; used[j] = true; local tmp = permute[j]; permute[j] = permute[i]; permute[i] = tmp; rem = rem - 2; end end return permute; end local inverse_table = function(tbl) local ret = {}; for i = 1,#tbl do ret[ tbl[i] ] = i; end return ret; end local rotors = {}; -- { permutation, work index }} local invrotors = {}; -- reversed tables -- SETUP REFLECTOR, ROTORS AND PLUGBOARD! local enigma_charcount = 127-32+1; -- n = 96 local enigma_charstart = 32; local reflector = reflector(enigma_charcount,settings.reflector); -- this is permutation U such that U^2 = id -- local plugboard = permutation(enigma_charcount,settings.plugboard,1); -- setup plugboard for enigma machine local invplugboard = inverse_table(plugboard); for i = 1,3 do rotors[i] = {rotor = permutation(enigma_charcount,settings.rotors[3 + i],1), idx = 0} end -- set up 3 rotors together with their indices for i = 1,3 do invrotors[i] = {rotor = inverse_table(rotors[i].rotor)} end -- set up 3 rotors together with their indices -- how many possible setups: --[[ n = charcount; rotors positions: n^3 plugboard wiring : n! reflector wiring: n! / (2^n * (n/2)!) TOTAL: (n!)^2*n^3 / ( 2^n * (n/2)! ) ~ 6.4 * 10^77 rotor positions & plugboard wiring: (n!)*n^3 ~ 4.8 * 10^57 (n=43) --]] -- END OF SETUP local rotate_rotor = function(i) local carry = 1; for j = i,1,-1 do local idx = rotors[j].idx; idx = idx + 1; if idx>=enigma_charcount then carry = 1; else carry = 0; end rotors[j].idx = idx % enigma_charcount; if carry == 0 then break end end end local enigma_encrypt_char = function(x) -- x : 1 .. enigma_charcount -- E = P.R1.R2.R3.U.R3^-1.R2^-1.R1^-1.P^-1, P = plugboard, R = rotor, U = reflector x = plugboard[x]; for i = 1,3 do local idx = rotors[i].idx; x = rotors[i].rotor[((x+idx-1) % enigma_charcount)+1]; end x = reflector[x]; for i = 3,1,-1 do local idx = rotors[i].idx; x = invrotors[i].rotor[x]; x = ((x-1-idx) % enigma_charcount)+1 end x = invplugboard[x]; -- apply rotation to rotor - and subsequent rotors if necessary rotate_rotor(3) return x; end --enigma_encrypt = function(input) settings.encrypt = function(input) -- rotor settings! rotors[1].idx = settings.rotors[1] rotors[2].idx = settings.rotors[2] rotors[3].idx = settings.rotors[3] local ret = ""; for i = 1,#input do local c = string.byte(input,i) - enigma_charstart +1; --say(i .. " : " .. c) if c>=1 and c<=enigma_charcount then c = enigma_encrypt_char(c); end ret = ret .. string.char(enigma_charstart+c-1); end return ret end settings.decrypt = settings.encrypt end end msg = self.listen_msg() if msg then msg = minetest.strip_colors(msg) local mark = string.find(msg,"@e") -- delimiter in chat if mark then msg = string.sub(msg,mark+2); msg = minetest.colorize("yellow",enigma_encrypt(msg)) say(minetest.colorize("red","#decrypted : ") .. msg) end end msg = self.sent_msg() if msg then local msg = enigma_encrypt(msg); say("@e" .. msg,true) -- minetest.show_formspec("encrypted_text", "size[4,4] textarea[0,-0.25;5,5;text;;".. "@e" .. minetest.formspec_escape(msg) .. "]") end