From 448f307184c82795c0018809392c050f2d8f2874 Mon Sep 17 00:00:00 2001 From: Smaug123 Date: Mon, 11 Jan 2016 23:04:28 +0000 Subject: [PATCH] Implement Enigma encryption/decryption. --- README.md | 61 ++++++++++ src/ClassicalCiphers.jl | 2 + src/common.jl | 10 +- src/enigma.jl | 240 ++++++++++++++++++++++++++++++++++++++++ test/enigma.jl | 64 +++++++++++ test/runtests.jl | 3 +- 6 files changed, 378 insertions(+), 2 deletions(-) create mode 100644 src/enigma.jl create mode 100644 test/enigma.jl diff --git a/README.md b/README.md index 496ffdd..0ea15d5 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ The Solitaire cipher is included for completeness, though it is perhaps not stri * [Portas] * [Hill] * [Playfair] +* [Enigma (M3 Army)][Enigma] * [Solitaire] ## Gotchas @@ -284,6 +285,65 @@ and then padding 'X's were introduced to ensure no digraph was a double letter. Finally, an 'X' was appended to the string, to ensure that the string was not of odd length. +### Enigma + +The variant of Enigma implemented is the M3 Army version. +This has five possible rotors, of which three are chosen in some distinct order. + +The plugboard may be specified either as a `Array{Tuple{Char, Char}}` or a string. +For example, both the following plugboards have the same effect: + +```julia +"ABCDEF" +[('A', 'B'), ('C', 'D'), ('E', 'F')] +``` + +For no plugboard, use `Tuple{Char, Char}[]` or `""`. + +The rotor order may be specified as `[5, 1, 2]` indicating that the leftmost rotor should be rotor 5, the middle should be rotor 1, and the rightmost should be rotor 2. +That is, when a letter goes into Enigma, it passes first through rotor 2, then rotor 1, then rotor 5. +(That is, letters move through the machine from right to left, before being reflected.) + +The ring settings may be specified as a three-character string. +For example, `"AAA"` indicates no adjustment to the rings. +TODO: expand this. + +The initial key may be specified as a three-character string. +For example, `"AQY"` indicates that the leftmost rotor should start at position `'A'`, the middle rotor at position `'Q'`, and the rightmost at position `'Y'`. + +Three reflectors are given; they may be specified with `reflector_id='A'` or `'B'` or `'C'`. +Alternatively, specify `reflector_id="YRUHQSLDPXNGOKMIEBFZCWVJAT"` to use a custom reflector; this particular example happens to be reflector `'B'`, so is equivalent to `reflector_id='B'`. + +For example, the following encrypts `"AAA"` with rotors 1, 2, 3, with key `"ABC"`, an empty plugboard, the default `'B'` reflector, and ring `"AAA"`: + +```julia +encrypt_enigma("AAA", [1,2,3], "ABC") +# outputs "CXT" +``` + +This is synonymous with: + +```julia +encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker="") +``` + +And also with: + +```julia +encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id="YRUHQSLDPXNGOKMIEBFZCWVJAT", stecker="") +``` + +And also with: + +```julia +encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker=Tuple{Char, Char}[]) +``` + +The arguments to `decrypt_enigma` are identical. +(In fact, `decrypt_enigma` and `encrypt_enigma` are essentially the same function, because Enigma is reversible.) +As ever, `encrypt_enigma` uppercases its input, and `decrypt_enigma` lowercases it. + + ### Solitaire cipher Encrypt the text "Hello, World!" with the Solitaire cipher, key "crypto": @@ -308,3 +368,4 @@ decrypt_solitaire("EXKYI ZSGEH UNTIQ", collect(1:54)) [Portas]: http://practicalcryptography.com/ciphers/porta-cipher/ [Hill]: https://en.wikipedia.org/wiki/Hill_cipher [Playfair]: https://en.wikipedia.org/wiki/Playfair_cipher +[Enigma]: https://en.wikipedia.org/wiki/Enigma_machine diff --git a/src/ClassicalCiphers.jl b/src/ClassicalCiphers.jl index 34e22f2..c49ea39 100644 --- a/src/ClassicalCiphers.jl +++ b/src/ClassicalCiphers.jl @@ -11,6 +11,7 @@ include("portas.jl") include("affine.jl") include("hill.jl") include("playfair.jl") +include("enigma.jl") export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic, encrypt_caesar, decrypt_caesar, crack_caesar, @@ -20,6 +21,7 @@ export encrypt_monoalphabetic, decrypt_monoalphabetic, crack_monoalphabetic, encrypt_solitaire, decrypt_solitaire, encrypt_hill, decrypt_hill, encrypt_playfair, decrypt_playfair, + encrypt_enigma, decrypt_enigma, string_fitness, index_of_coincidence end # module diff --git a/src/common.jl b/src/common.jl index de81d55..9b4be39 100644 --- a/src/common.jl +++ b/src/common.jl @@ -21,7 +21,15 @@ function rotateLeft(arr, n) ans end -flatten{T}(a::Array{T,1}) = any(map(x->isa(x,Array),a))? flatten(vcat(map(flatten,a)...)): a +function rotateLeftStr(st::AbstractString, n) + join(rotateLeft(split(st, ""), n), "") +end + +function rotateRightStr(st::AbstractString, n) + join(rotateRight(split(st, ""), n), "") +end + +flatten{T}(a::Array{T,1}) = any(x->isa(x,Array),a)? flatten(vcat(map(flatten,a)...)): a flatten{T}(a::Array{T}) = reshape(a,prod(size(a))) flatten(a)=a diff --git a/src/enigma.jl b/src/enigma.jl new file mode 100644 index 0000000..b17a3b8 --- /dev/null +++ b/src/enigma.jl @@ -0,0 +1,240 @@ +import Base.uppercase + +function uppercase(a::Tuple{Char, Char}) + (uppercase(a[1]), uppercase(a[2])) +end + +function parse_stecker(stecker::AbstractString) + if length(stecker) % 2 != 0 + error("Stecker setting must be of even length.") + end + + if stecker == "" + steck_parsed = Tuple{Char, Char}[] + else + sp = split(stecker, "") + steck_parsed = [(sp[i][1], sp[i+1][1]) for i in 1:2:length(sp)] + end + steck_parsed +end + +function parse_stecker(stecker::Array{Tuple{Char, Char}}) + if stecker == [] + return Array{Tuple{Char, Char}, 1}() + else + return stecker + end +end + +function parse_reflector(reflector::Char) + if uppercase(reflector) == 'A' + return "EJMZALYXVBWFCRQUONTSPIKHGD" + elseif uppercase(reflector) == 'B' + return "YRUHQSLDPXNGOKMIEBFZCWVJAT" + elseif uppercase(reflector) == 'C' + return "FVPJIAOYEDRZXWGCTKUQSBNMHL" + else + error("Reflector $(reflector) unrecognised.") + end +end + +function parse_reflector(reflector::AbstractString) + if length(reflector) != 26 + error("Reflector must be a 26-char string.") + end + + ans = uppercase(reflector) + + if ans != join(unique(ans), "") + error("Reflector must not contain any character used more than once.") + end + + ans +end + +""" +Encrypts the given plaintext according to the Enigma (M3, army version). + +Arguments are in the order: plaintext, stecker, rotors, ring, key. + +Plaintext is a string; punctuation is stripped out and it is made lowercase. +Rotors is an array - for example, [1,2,3] - being the order of the rotors. + Each entry should be a distinct integer between 1 and 5 inclusive. +Key is a string of three letters, indicating the starting positions of the rotors. + +Optional: +reflector_id='B', which sets whether to use reflector A, B or C. + Can also be specified as a 26-char string. +Stecker is either an array - for example, [('A','B'), ('D', 'E')] specifying + that A, B are swapped and D, E are swapped - or a string ("ABDE" accomplishing + the same thing). No letter may appear more than once. +Ring is a string - for example, "AAA" - being the offset applied to each rotor. + "AAA", for example, signifies no offset. The string must be three letters. +""" +function encrypt_enigma{I <: Integer}(plaintext, + rotors::Array{I, 1}, key::AbstractString; + reflector_id='B', ring::AbstractString = "AAA", + stecker = Tuple{Char, Char}[]) + parsed_stecker = parse_stecker(stecker) + # validate stecker settings + if flatten(parsed_stecker) != unique(flatten(parsed_stecker)) + error("No letter may appear more than once in stecker settings.") + end + parsed_stecker = map(uppercase, parsed_stecker) + + + # validate ring settings + if length(ring) != 3 + error("Ring settings must be a string of length 3.") + end + ring = uppercase(ring) + for ch in ring + if !('A' <= ch <= 'Z') + error("Ring settings must be a string of Roman letters.") + end + end + + # validate key settings + if length(key) != 3 + error("Key settings must be a string of length 3.") + end + key = uppercase(key) + for ch in key + if !('A' <= ch <= 'Z') + error("Key settings must be a string of Roman letters.") + end + end + + # validate rotor settings + for i in rotors + if !(1 <= i <= 5) + error("Each rotor must be an integer between 1 and 5.") + end + end + if rotors != unique(rotors) + error("No rotor may appear more than once.") + end + + # validate reflector settings + reflector = parse_reflector(reflector_id) + + # sanitise plaintext + plaintext = uppercase(letters_only(plaintext)) + + # initialisation of the machine + + rotor_layouts = ["EKMFLGDQVZNTOWYHXUSPAIBRCJ", + "AJDKSIRUXBLHWTMCQGZNPYFVOE", + "BDFHJLCPRTXVZNYEIWGAKMUSQO", + "ESOVPZJAYQUIRHXLNFTGKDCMWB", + "VZBRGITYUPSDNHLXAWMJQOFECK"] + notches = [17,5,22,10,26] + + rotor1 = rotor_layouts[rotors[1]] + notch1 = notches[rotors[1]] + rotor2 = rotor_layouts[rotors[2]] + notch2 = notches[rotors[2]] + rotor3 = rotor_layouts[rotors[3]] + notch3 = notches[rotors[3]] + + # apply the key as part of initialisation; incorporates ring + key_offsets = [26+Int(ch)-65 for ch in key] + notch1 = (key_offsets[1]*26+notch1-key_offsets[1]) % 26 + notch2 = (key_offsets[2]*26+notch2-key_offsets[2]) % 26 + notch3 = (key_offsets[3]*26+notch3-key_offsets[3]) % 26 + + key_offsets = key_offsets .- [Int(ring[i])-65 for i in 1:3] + + # We receive a character; the rotors increment; then: + # the character goes through the plugboard + # the character then goes through rotor3, then rotor2, then rotor1 + # then the reflector, then the inverse of rotor 1, 2, 3 + # finally the plugboard again + + plugboard_dict = Dict([parsed_stecker; map(reverse, parsed_stecker)]) + + ans = IOBuffer() + + rotor3movements = key_offsets[3] + rotor2movements = key_offsets[2] + rotor1movements = key_offsets[1] + + for i in 1:length(plaintext) + + working_ch = plaintext[i] + + # rotate rotors + notch3 -= 1 + rotor3movements += 1 + if notch3 == 0 + notch3 = 26 + + rotor2movements += 1 + notch2 -= 1 + if notch2 == 0 + notch2 = 26 + rotor1movements += 1 + notch1 -= 1 + + if notch1 == 0 + notch1 = 26 + end + end + end + + # double step of rotor + if notch3 == 25 && notch2 == 1 + notch2 = 26 + rotor2movements += 1 + rotor1movements += 1 + notch1 -= 1 + if notch1 == 0 + notch1 = 26 + end + end + + + # plugboard + working_ch = encrypt_monoalphabetic(working_ch, plugboard_dict)[1] + + # rotors + # comes in as… + working_ch = Char(65+((rotor3movements+Int(working_ch)-65) % 26)) + working_ch = encrypt_monoalphabetic(working_ch, rotor3)[1] + + # comes in as… + working_ch = Char(65+(((26*rotor3movements)-rotor3movements+rotor2movements+Int(working_ch)-65) % 26)) + working_ch = encrypt_monoalphabetic(working_ch, rotor2)[1] + + # comes in as… + working_ch = Char((((26*rotor2movements) + Int(working_ch)-65 - rotor2movements + rotor1movements) % 26) + 65) + working_ch = encrypt_monoalphabetic(working_ch, rotor1)[1] + + # reflector + # comes in as… + working_ch = Char((26*rotor1movements + Int(working_ch) - 65 - rotor1movements) % 26 + 65) + working_ch = encrypt_monoalphabetic(working_ch, reflector)[1] + + # rotors + # comes in as… + working_ch = Char((Int(working_ch)-65+rotor1movements) % 26 + 65) + working_ch = uppercase(decrypt_monoalphabetic(working_ch, rotor1))[1] + working_ch = Char(65+((rotor1movements*26 + rotor2movements - rotor1movements +Int(working_ch)-65) % 26)) + working_ch = uppercase(decrypt_monoalphabetic(working_ch, rotor2))[1] + working_ch = Char(65+((26*rotor2movements + rotor3movements-rotor2movements+Int(working_ch)-65) % 26)) + working_ch = uppercase(decrypt_monoalphabetic(working_ch, rotor3))[1] + + # plugboard + # comes in as… + working_ch = Char(65+(((26*rotor3movements)-rotor3movements+Int(working_ch)-65) % 26)) + working_ch = encrypt_monoalphabetic(working_ch, plugboard_dict)[1] + + print(ans, working_ch) + end + + uppercase(takebuf_string(ans)) +end + +function decrypt_enigma(args1...; args2...) + lowercase(encrypt_enigma(args1...; args2...)) +end \ No newline at end of file diff --git a/test/enigma.jl b/test/enigma.jl new file mode 100644 index 0000000..01a8f1e --- /dev/null +++ b/test/enigma.jl @@ -0,0 +1,64 @@ +using ClassicalCiphers +using Base.Test + +@test (encrypt_enigma("AAA", [1,2,3], "AAA") == "BDZ") +@test (decrypt_enigma("BDZ", [1,2,3], "AAA") == "aaa") + +@test (encrypt_enigma("AAA", [1,2,3], "ABC") == "CXT") +@test (decrypt_enigma("CXT", [1,2,3], "ABC") == "aaa") + +@test (encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker="") == "CXT") +@test (decrypt_enigma("CXT", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker="") == "aaa") + +@test (encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id="YRUHQSLDPXNGOKMIEBFZCWVJAT", stecker="") == "CXT") +@test (decrypt_enigma("CXT", [1,2,3], "ABC", ring="AAA", reflector_id="YRUHQSLDPXNGOKMIEBFZCWVJAT", stecker="") == "aaa") + +@test (encrypt_enigma("AAA", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker=Tuple{Char, Char}[]) == "CXT") +@test (decrypt_enigma("CXT", [1,2,3], "ABC", ring="AAA", reflector_id='B', stecker=Tuple{Char, Char}[]) == "aaa") + +@test encrypt_enigma("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", [1,2,3], "AAA") == "BDZGOWCXLTKSBTMCDLPBMUQOFXYHCXTGYJFL" +@test decrypt_enigma("BDZGOWCXLTKSBTMCDLPBMUQOFXYHCXTGYJFL", [1,2,3], "AAA") == "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + +@test (stecker = [('P','O'), ('M','L'), ('I','U'), ('K','J'), ('N','H'), ('Y','T'), ('G','B'), ('V','F'), ('R','E'), ('D','C')]; + encrypt_enigma("ABCDEF", [1,2,3], "AAA", stecker=stecker) == "GWKGRC") +@test (stecker = [('P','O'), ('M','L'), ('I','U'), ('K','J'), ('N','H'), ('Y','T'), ('G','B'), ('V','F'), ('R','E'), ('D','C')]; + decrypt_enigma("GWKGRC", [1,2,3], "AAA", stecker=stecker) == "abcdef") + +@test (encrypt_enigma("there are fifteen possible answers to this question", + [1,2,3], + "UQI") == "SPPBXWOFVOEAKDRFKLDOLYHKSNTFBPERQFZCDTRAKXCE") +@test (decrypt_enigma("SPPBXWOFVOEAKDRFKLDOLYHKSNTFBPERQFZCDTRAKXCE", + [1,2,3], + "UQI") == "therearefifteenpossibleanswerstothisquestion") + +@test (encrypt_enigma("AAAAAAAA", [1,2,3], "UQI") == "OWLDUWEL") +@test (decrypt_enigma("OWLDUWEL", [1,2,3], "UQI") == "aaaaaaaa") + +@test (encrypt_enigma("AAAAAAAA", [1,2,3], "UQI", stecker="") == "OWLDUWEL") +@test (decrypt_enigma("OWLDUWEL", [1,2,3], "UQI", stecker="") == "aaaaaaaa") + +@test (encrypt_enigma("AAAAAAAA", [1,2,3], "AAA", ring="AAB") == "UBDZGOWC") +@test (decrypt_enigma("UBDZGOWC", [1,2,3], "AAA", ring="AAB") == "aaaaaaaa") + +@test (encrypt_enigma("AAAA", [1,2,3], "UQI", ring="EFG") == "YHKD") +@test (decrypt_enigma("YHKD", [1,2,3], "UQI", ring="EFG") == "aaaa") + +@test (encrypt_enigma("when shall we three meet again", [1,2,3], "yev", ring="aaa", stecker="POMLIUKJNHYTGBVFREDC") == "PDTTELKJEYFAQZCHRWVREXFFK") + +@test (encrypt_enigma("when shall we three meet again", [5,1,4], "yev", ring="aaa", stecker="POMLIUKJNHYTGBVFREDC") == "TSJKKEKWKLFPOCKAXUVSDWVOW") + +@test (encrypt_enigma("WHENSHALLWETHREEMEETAGAIN", [5, 1, 4], "yev", ring="aac", stecker="POMLIUKJNHYTGBVFREDC") == "AVYFURCOELTCLFMZGABKOWDKH") + +@test (encrypt_enigma("WHENSHALLWETHREEMEETAGAIN", [5, 1, 4], "yev", ring="aab", stecker="POMLIUKJNHYTGBVFREDC") == "OIXYQZDEWUHADNZKXBUGZZNQW") + +@test (encrypt_enigma("WHENSHALLWETHREEMEETAGAIN", [5, 1, 4], "yev", ring="nqz", stecker="POMLIUKJNHYTGBVFREDC") == "GDCTXXBEIGWEMLLUDNUXZJDKU") + +@test (encrypt_enigma("AAA", [1,2,3], "QEV") == "LNP") + +@test (plain = "WHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAIN"; + ans = "TSJKKEKWKLFPOCKAXUVSDWVOWSSDCKBGJDNBJYWZPOFKMSMQKMFQVZKGHFWHWEMKXLELAXRRNHWLCBXAQWUEPVEVXWGPMDVNXLHLBGLALOYQKLMKOJBWWOMUGXNPCZRLQDMWRCDNNENKNGGYBYOUMQNFKJPMQANZLRAJIGDABYMWGYWQZVILLMSQAPQGPZLHXKLGMZEMJJYJGAYCTHRVAKRJNXWHFLOARTZICGUODQTNPBMTGXOHIUBIDXNVBZVWSGSHVVGFHKRUWQCKMLKZEUAZDHUUFPPKKOFIDSIPNGVOLYFCWZYVKPGJPTHLVJPUNRNUTUTKYGZYHCEZIOAMCCUJNEPWKGZNMOBMCKHVWJLZGFZHUHTPBLGTVDIKKDVUVCWWOYAXNQWUROBHHYWSHSPUULKPUVAOLTVZWNRVOEEREEXHYOVKRBZIKCWKPVYUATVPDZUNJPMNDXIKQHPVNBDWEERCBBSABVEQOMGEPJWIOMVEVLKZXORJCYYDELDYKKJIZSBVNTTJEYPNSYKMAJKZIFVDQPHJOBHTTDZBLZMLTPTLHDPKWHSWAGVIEUADKUFRSFYSAOUGIZWWYZPXEOQJXXXFXSQZGDBEGNRDIPVXNVVGWMEFMHYVHMHURIVNINPRQWYEDG"; + encrypt_enigma(plain, [5, 1, 4], "yev", ring="aaa", stecker="POMLIUKJNHYTGBVFREDC") == ans) + +@test (plain = "WHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAINWHENSHALLWETHREEMEETAGAIN"; + ans = "UGVATRLVMDBJBTUUVZOJZRICCJPJVKLZTHIMVRDRTWTSGWLWGBLPRDITLGEUWIJQCDQKZXFTCYYNJSBMLBPWQSNWJUFJKHSDRDEQCZHVXLECUNQENXFCLJJHYXULFHZQLYQPGBBHDQLUJKUXFPHGVKSPRDWFGXJYXFGCVHNODSZRSSHQTLUXTHJEZAXKYFRYWWNEJQDJUFFPASFEHOGETXSVVIWKKITUWALMTWCPGZLKOSJYDFUJHUYCSHMFBPMKVYRNQDEBQFQTFJKYIBTNLMQAQLDAMLAIJKGVGVWLBQOCPOQJXQYYUCXVEXPIHOGMDFUYCJCOIKDZIHHAJYAKHXRBHYANVKMQSXVYCTAKUNPPHSTKPEDFQUXAQSQZNLXWDNXLJTFLBWBQNZMKLUJKNUEARFIXAFLMVNRZMMGEHGFLZONEJQMUCXYZBJLNJSZTZMPAVVIJJMDQOJRXYOAFHINLKNFPASLEPKUHRMKXTNOBLSPXMPQSISGJLXBEXSOYCOVLESCHDASAWKKMWDXGNMTJBLWBKGMGXEDCJMICXKIBCBKUOQNPRUGTIUVODQVPKRGWWQWTYLIPCRSNUBHJWQBXCGNCYJRNGNCYZIMXHNHNZPTSEFRYQPAGJJOTXVMMKQLSMGBJNW"; + encrypt_enigma(plain, [5, 1, 4], "yev", ring="ezk", stecker="POMLIUKJNHYTGBVFREDC") == ans) diff --git a/test/runtests.jl b/test/runtests.jl index 3fd7540..1afa521 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,8 @@ using ClassicalCiphers tests = ["vigenere", "monoalphabetic", "solitaire", - "caesar", "portas", "affine", "hill", "playfair"] + "caesar", "portas", "affine", "hill", "playfair", + "enigma"] println("Running tests:")