Copyright | (c) 2014-2019 Roy Levien |
---|---|
License | BSD3 |
Maintainer | royl@aldaron.com |
Stability | provisional |
Portability | POSIX |
Safe Haskell | Safe |
Language | Haskell2010 |
Crypto.Enigma
Description
An Enigma machine simulator with rudimentary display, currently limited to the I, M3, and M4 models.
Richer display is provided by Crypto.Enigma.Display.
Synopsis
- data Component
- name :: Component -> Name
- wiring :: Component -> Wiring
- turnovers :: Component -> Turnovers
- type Name = String
- type Wiring = Mapping
- type Turnovers = String
- component :: Name -> Component
- rotors :: [Name]
- reflectors :: [Name]
- data EnigmaConfig
- configEnigma' :: String -> String -> String -> String -> Either EnigmaError EnigmaConfig
- configEnigma :: String -> String -> String -> String -> EnigmaConfig
- stages :: EnigmaConfig -> [Stage]
- components :: EnigmaConfig -> [Name]
- positions :: EnigmaConfig -> [Position]
- rings :: EnigmaConfig -> [Int]
- windows :: EnigmaConfig -> String
- type Position = Int
- type Stage = Int
- step :: EnigmaConfig -> EnigmaConfig
- type Mapping = String
- data Direction
- componentMapping :: Direction -> Component -> Position -> Mapping
- stageMappingList :: EnigmaConfig -> [Mapping]
- enigmaMappingList :: EnigmaConfig -> [Mapping]
- enigmaMapping :: EnigmaConfig -> Mapping
- type Message = String
- message :: String -> Message
- enigmaEncoding :: EnigmaConfig -> Message -> String
Machine components
A component used to construct an Enigma machine (embodied in an EnigmaConfig
) identified by its name
, and
characterized by its physical wiring
and additionally — for rotors other than the reflector — by turnovers
which govern the step
ping of the machine in which it is installed.
A string identifying a Component
of an Enigma machine.
For rotors (including the reflector) this is one of the conventional letter or Roman numeral designations
(e.g., "IV"
or "β"
). For the plugboard this is the conventional string of letter pairs (separated by periods),
indicating letters wired together by plugging (e.g., "AU.ZM.ZL.RQ"
).
Absence or non-use of a plugboard can be indicated with a lone "~". See name
.
type Turnovers = String Source #
The list of letters on the rotor's ring that appear at the window when a Component'
s ring is in the turnover
position.
Not applicable (and empty) for the plugboard and for reflectors. See turnovers
.
Machine configurations and transitions
data EnigmaConfig Source #
The complete description of the state of an Enigma machine, consisting of components
, positions
, and rings
.
Two functions (configEnigma'
and configEnigma
) are provided for creating EnigmaConfig
, which differ in
their handling of errors in the provided arguments specifying the configuration (the only potential source of errors,
since all other arguments throught the package are coerced to valid values).
Instances
Eq EnigmaConfig Source # | |
Defined in Crypto.Enigma | |
Read EnigmaConfig Source # | Read the elements of a conventional specification (see
|
Defined in Crypto.Enigma Methods readsPrec :: Int -> ReadS EnigmaConfig # readList :: ReadS [EnigmaConfig] # | |
Show EnigmaConfig Source # | Show the elements of a conventional specification (see
|
Defined in Crypto.Enigma Methods showsPrec :: Int -> EnigmaConfig -> ShowS # show :: EnigmaConfig -> String # showList :: [EnigmaConfig] -> ShowS # |
configEnigma' :: String -> String -> String -> String -> Either EnigmaError EnigmaConfig Source #
Create an EnigmaConfig
from a conventional specification.
A (safe public, "smart", total) constructor intended for use in pure code that does validation and takes a conventional specification as input, in the form of four strings:
- The rotor
Name
s, separated by dashes (e.g."C-V-I-II"
); seeName
. - The letters visible at the windows (e.g.
"MQR"
); seewindows
. - The plugboard specification (which may be omitted with
"~"
); seeName
. - The position of the letter ring on each rotor, separated by periods (e.g.
"22.11.16"
); seerings
.
Following convention, the elements of these strings are in physical machine order as the operator sees
them, which is the reverse of the order in which they are encountered in processing (see stages
).
Validation is permissive, allowing for ahistorical collections and numbers of rotors (including reflectors
at the rotor stage, and trivial degenerate machines;
e.g., configEnigma "-" "A" "" "01"
), and any number of (non-contradictory) plugboard wirings (including none).
Invalid arguments return an EnigmaError
:
>>>
configEnigma' "c-β-V-III-II" "LQVI" "AM.EU.ZiL" "16.01.21.11"
Left Bad plugboard: AM.EU.ZiL
configEnigma :: String -> String -> String -> String -> EnigmaConfig Source #
Create an EnigmaConfig
from a conventional specification.
A thin convenience wrapper on configEnigma'
intended for most uses (e.g., interactive) that takes the same
arguments but errors with an informative message and a stack trace:
>>>
configEnigma "c-β-V-III-II" "LQVI" "AM.EU.ZiL" "16.01.21.11"
*** Exception: Bad plugboard: AM.EU.ZiL CallStack (from HasCallStack): error, called at crypto-enigma/Crypto/Enigma.hs:317:21 in main:Crypto.Enigma
This should be used instead of read
, which cannot report error details:
>>>
read "c-β-V-III-II LQVI AM.EU.ZiL 16.01.21.11" :: EnigmaConfig
*** Exception: Prelude.read: no parse
stages :: EnigmaConfig -> [Stage] Source #
The sequential, (forward) processing-order, Stage
occupied by each Component
in an EnigmaConfig
, starting
with 0
for the plugboard and ending with the reflector.
>>>
stages $ configEnigma "c-β-V-III-II" "LQVI" "AM.EU.ZL" "16.01.21.11"
[0,1,2,3,4,5]
components cfg == ((components cfg !!) <$> stages cfg)
The term 'stage' (lowercase) is also used here to encompass subsequent reverse processing order stages
(see, for example, stageMappingList
).
components :: EnigmaConfig -> [Name] Source #
The Name
of each Component
in an EnigmaConfig
, in processing order.
Unchanged by step
.
>>>
components $ configEnigma "c-β-V-III-II" "LQVI" "AM.EU.ZL" "16.01.21.11"
["AM.EU.ZL","II","III","V","\946","c"]
(Note that any Unicode characters are
stored by Haskell as their Unicode value:
here "\946" == "β"
.)
positions :: EnigmaConfig -> [Position] Source #
The Position
of each Component
in an EnigmaConfig
, in machine processing order.
May be changed by step
.
>>>
positions $ configEnigma "c-β-V-III-II" "LQVI" "AM.EU.ZL" "16.01.21.11"
[1,25,2,17,23,1]
For plugboard and reflector, this will always be 1
since the former cannot rotate,
and the latter does not (neither will be changed by step
):
head (positions cfg) == 1
last (positions cfg) == 1
This determines the encoding performed by a component (see componentMapping
).
rings :: EnigmaConfig -> [Int] Source #
The location of ring letter A
on the rotor for each Component
in an EnigmaConfig
,
in machine processing order.
Unchanged by step
.
>>>
rings $ configEnigma "c-β-V-III-II" "LQVI" "AM.EU.ZL" "16.01.21.11"
[1,11,21,1,16,1]
For plugboard and reflector, this will always be 1
since the former lacks a ring,
and for latter ring position is irrelevant (the letter ring is not visible, and has
no effect on when turnovers occur):
head (rings cfg) == 1
last (rings cfg) == 1
windows :: EnigmaConfig -> String Source #
The letters at the window in an EnigmaConfig
, in physical, conventional order.
This is the (only) visible manifestation of configuration changes during operation.
>>>
windows $ configEnigma "c-β-V-III-II" "LQVI" "AM.EU.ZL" "16.01.21.11"
"LQVI"
The generalized rotational position of a Component
. For rotors, this is denoted by number on the rotor
(not letter ring) that is at the "window position". For other components the only meaningful position is 1
(see positions
).
This (alone) determines the permutations applied to the component's Wiring
to produce its current Mapping
(see componentMapping
).
step :: EnigmaConfig -> EnigmaConfig Source #
Step the machine to a new EnigmaConfig
by rotating the rightmost (first) rotor one position, and other rotors
as determined by the positions
of rotors in the machine.
In the physical machine, a step occurs in response to each operator keypress,
prior to processing that key's letter. (See enigmaEncoding
.)
Stepping leaves the components
, stages
and rings
of a configuration unchanged, changing only positions
,
which is manifest in changes of the letters at the windows
:
>>>
let cfg = configEnigma "c-γ-V-I-II" "LXZO" "UX.MO.KZ.AY.EF.PL" "03.17.04.01"
>>>
putStr $ unlines $ show <$> take 5 (iterate step cfg)
c-γ-V-I-II LXZO UX.MO.KZ.AY.EF.PL 03.17.04.01 c-γ-V-I-II LXZP UX.MO.KZ.AY.EF.PL 03.17.04.01 c-γ-V-I-II LXZQ UX.MO.KZ.AY.EF.PL 03.17.04.01 c-γ-V-I-II LXZR UX.MO.KZ.AY.EF.PL 03.17.04.01 c-γ-V-I-II LXZS UX.MO.KZ.AY.EF.PL 03.17.04.01
>>>
let cfg = configEnigma "c-γ-V-I-II" "LXZO" "UX.MO.KZ.AY.EF.PL" "03.17.04.01"
>>>
take 5 $ map windows $ iterate step cfg
["LXZO","LXZP","LXZQ","LXZR","LXZS"]
>>>
let cfg = configEnigma "c-γ-V-I-II" "LXZO" "UX.MO.KZ.AY.EF.PL" "03.17.04.01"
>>>
take 5 $ map positions $ iterate step cfg
[[1,15,23,8,10,1],[1,16,23,8,10,1],[1,17,23,8,10,1],[1,18,23,8,10,1],[1,19,23,8,10,1]]
Mappings
The encodings established by the machine and its components.
type Mapping = String Source #
The mapping used by a component (see wiring
and componentMapping
)
or by the machine (see enigmaMapping
) to perform a
simple substitution encoding.
This is expressed as a string of letters indicating the mapped-to letter
for the letter at that position in the alphabet — i.e., as a permutation of the alphabet.
For example, the mapping EKMFLGDQVZNTOWYHXUSPAIBRCJ
encodes A
to E
, B
to K
, C
to M
, ...
Y
to C
, and Z
to J
.
The direction that a signal flows through a Component
. During encoding of a character, the signal
passes first through the wiring of each component, from right to left in the machine, in a forward (Fwd
)
direction, then through the reflector, and then, from left to right, through each component again,
in reverse (Rev
).
This direction affects the encoding performed by the component (see componentMapping
).
componentMapping :: Direction -> Component -> Position -> Mapping Source #
The Mapping
performed by a Component
as a function of its Position
and the Direction
of the
signal passing through it.
The base encoding of a Component
, performed with rotor position 1
at the window, is set by its wiring
.
componentMapping Fwd comp 1 == wiring comp
For all other positions, the encoding is a cyclic permutation this mapping's inputs (backward)
and outputs (forward) by the rotational offset of the rotor away from the 1
position
(though in an actual EmigmaConfig
such positions occur only for rotors; see positions
).
Note that because the wiring of reflectors generates mappings that consist entirely of paired exchanges of letters, reflectors (at any position) produce the same mapping in both directions (the same is true of the plugboard):
>>>
let tst c n = componentMapping Fwd (component c) n == componentMapping Rev (component c) n
>>>
and $ tst <$> ["A","B","C","b","c"] <*> [1..26]
True
stageMappingList :: EnigmaConfig -> [Mapping] Source #
The list of Mapping
s for each stage of an EnigmaConfig
: the encoding performed by the
Component
at that point in the progress through the machine.
These are arranged in processing order, beginning with the encoding performed by the plugboard,
followed by the forward encoding performed by each rotor (see componentMapping
), then the reflector,
followed by the reverse encodings by each rotor, and finally by the plugboard again.
>>>
putStr $ unlines $ stageMappingList (configEnigma "b-γ-V-VIII-II" "LFAQ" "UX.MO.KZ.AY.EF.PL" "03.17.04.11")
YBCDFEGHIJZPONMLQRSTXVWUAK LORVFBQNGWKATHJSZPIYUDXEMC BJYINTKWOARFEMVSGCUDPHZQLX ILHXUBZQPNVGKMCRTEJFADOYSW YDSKZPTNCHGQOMXAUWJFBRELVI ENKQAUYWJICOPBLMDXZVFTHRGS PUIBWTKJZSDXNHMFLVCGQYROAE UFOVRTLCASMBNJWIHPYQEKZDXG JARTMLQVDBGYNEIUXKPFSOHZCW LFZVXEINSOKAYHBRGCPMUDJWTQ YBCDFEGHIJZPONMLQRSTXVWUAK
Note that, because plugboard Mapping
is established by paired exchanges of letters
(see componentMapping
),
head (stageMappingList cfg) == last (stageMappingList cfg)
As noted (see stages
) the term 'stage' here encompasses reverse processing:
length (stageMappingList cfg) == 2 * length (stages cfg) - 1
A richer example of how this list is used, and how it can be interpreted, can be found in Crypto.Enigma.Display.
enigmaMappingList :: EnigmaConfig -> [Mapping] Source #
The list of Mapping
s an EnigmaConfig
has performed by each stage:
the encoding performed by the EnigmaConfig
up to that point in the progress through the machine.
>>>
putStr $ unlines $ enigmaMappingList (configEnigma "b-γ-V-VIII-II" "LFAQ" "UX.MO.KZ.AY.EF.PL" "03.17.04.11")
YBCDFEGHIJZPONMLQRSTXVWUAK MORVBFQNGWCSJHTAZPIYEDXULK EVCHJTGMKZYUAWDBXSOLNIQPFR UDHQNFZKVWSAIOXLYJCGMPTRBE BKNUMPIGREJYCXLQVHSTOAFWDZ NCBFPMJYXAIGKRODTWZVLEUHQS HIUTFNSAOPZKDVMBGREYXWQJLC CAEQTJYUWIGMVKNFLPRXDZHSBO RJMXFBCSHDQNOGELYUKZTWVPAI COYWEFZPNVGHBIXATUKQMJDRLS CMAWFEKLNVGHBIUYTXZQOJDRPS
Since these may be thought of as cumulative encodings,
enigmaMapping cfg == last (enigmaMappingList cfg)
enigmaMapping :: EnigmaConfig -> Mapping Source #
The Mapping
performed by the Enigma machine.
>>>
enigmaMapping (configEnigma "b-γ-V-VIII-II" "LFAQ" "UX.MO.KZ.AY.EF.PL" "03.17.04.11")
"CMAWFEKLNVGHBIUYTXZQOJDRPS"
A example of a richer display of this information can be found in Crypto.Enigma.Display.
Encoding
Encoding messages.
message :: String -> Message Source #
Convert a String
to valid Enigma machine input: replace any symbols for which there are standard Kriegsmarine
substitutions, remove any remaining non-letter characters, and convert to uppercase. This function is applied
automatically to String
s suppied as Message
arguments to functions in this package.
enigmaEncoding :: EnigmaConfig -> Message -> String Source #
Encode a Message
using a given (starting) machine configuration, by step
ping the configuration prior to
processing each character of the message. This produces a new configuration (with new positions
only)
for encoding each character, which serves as the "starting" configuration for subsequent
processing of the message.
>>>
enigmaEncoding (configEnigma "b-γ-V-VIII-II" "LFAP" "UX.MO.KZ.AY.EF.PL" "03.17.04.11") "KRIEG"
"GOWNW"
The details of this encoding and its relationship to stepping from one configuration to another are illustrated in Crypto.Enigma.Display.
Note that because of the way the Enigma machine is designed, it is always the case (provided that msg
is
all uppercase letters) that
enigmaEncoding cfg (enigmaEncoding cfg msg) == msg