Skip to content

Commit 24d7ba3

Browse files
committed
initial commit
0 parents  commit 24d7ba3

File tree

3 files changed

+224
-0
lines changed

3 files changed

+224
-0
lines changed

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# enigma recreation in go
2+
3+
## rotor diagram (from Wikipedia)
4+
| Rotor # | ABCDEFGHIJKLMNOPQRSTUVWXYZ | Date Introduced | Model Name & Number |
5+
|---------|-----------------------------|------------------|----------------------|
6+
| IC | DMTWSILRUYQNKFEJCAZBPGXOHV | 1924 | Commercial Enigma A, B |
7+
| IIC | HQZGPJTMOBLNCIFDYAWVEUSRKX | 1924 | Commercial Enigma A, B |
8+
| IIIC | UQNTLSZFMREHDPXKIBVYGJCWOA | 1924 | Commercial Enigma A, B |
9+
| I | JGDQOXUSCAMIFRVTPNEWKBLZYH | 7 February 1941 | German Railway (Rocket) |
10+
| II | NTZPSFBOKMWRCJDIVLAEYUXHGQ | 7 February 1941 | German Railway (Rocket) |
11+
| III | JVIUBHTCDYAKEQZPOSGXNRMWFL | 7 February 1941 | German Railway (Rocket) |
12+
| UKW | QYHOGNECVPUZTFDJAXWMKISRBL | 7 February 1941 | German Railway (Rocket) |
13+
| ETW | QWERTZUIOASDFGHJKPYXCVBNML | 7 February 1941 | German Railway (Rocket) |
14+
| I-K | PEZUOHXSCVFMTBGLRINQJWAYDK | February 1939 | Swiss K |
15+
| II-K | ZOUESYDKFWPCIQXHMVBLGNJRAT | February 1939 | Swiss K |
16+
| III-K | EHRVXGAOBQUSIMZFLYNWKTPDJC | February 1939 | Swiss K |
17+
| UKW-K | IMETCGFRAYSQBZXWLHKDVUPOJN | February 1939 | Swiss K |
18+
| ETW-K | QWERTZUIOASDFGHJKPYXCVBNML | February 1939 | Swiss K |
19+
| I | EKMFLGDQVZNTOWYHXUSPAIBRCJ | 1930 | Enigma I |
20+
| II | AJDKSIRUXBLHWTMCQGZNPYFVOE | 1930 | Enigma I |
21+
| III | BDFHJLCPRTXVZNYEIWGAKMUSQO | 1930 | Enigma I |
22+
| IV | ESOVPZJAYQUIRHXLNFTGKDCMWB | December 1938 | M3 Army |
23+
| V | VZBRGITYUPSDNHLXAWMJQOFECK | December 1938 | M3 Army |
24+
| VI | JPGVOUMFYQBENHZRDKASXLICTW | 1939 | M3 & M4 Naval (FEB 1942) |
25+
| VII | NZJHGRCXMYSWBOUFAIVLPEKQDT | 1939 | M3 & M4 Naval (FEB 1942) |
26+
| VIII | FKQHTLXOCBJSPDZRAMEWNIUYGV | 1939 | M3 & M4 Naval (FEB 1942) |
27+
| Beta | LEYJVCNIXWPBQMDRTAKZGFUHOS | Spring 1941 | M4 R2 |
28+
| Gamma | FSOKANUERHMBTIYCWLQPZXVGJD | Spring 1942 | M4 R2 |
29+
| Reflector A | EJMZALYXVBWFCRQUONTSPIKHGD | | |
30+
| Reflector B | YRUHQSLDPXNGOKMIEBFZCWVJAT | | |
31+
| Reflector C | FVPJIAOYEDRZXWGCTKUQSBNMHL | | |
32+
| Reflector B Thin | ENKQAUYWJICOPBLMDXZVFTHRGS | 1940 | M4 R1 (M3 + Thin) |
33+
| Reflector C Thin | RDOBJNTKVEHMLFCWZAXGYIPSUQ | 1940 | M4 R1 (M3 + Thin) |
34+
| ETW | ABCDEFGHIJKLMNOPQRSTUVWXYZ | | Enigma I |

internal/enigma/enigma.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package enigma
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"regexp"
8+
"strings"
9+
)
10+
11+
type Decoder struct {
12+
r io.Reader
13+
e enigma
14+
}
15+
16+
type Encoder struct {
17+
w io.Writer
18+
e enigma
19+
}
20+
21+
type enigma struct {
22+
rings []ring
23+
reflector reflector
24+
plugboard map[string]string
25+
}
26+
27+
type ring struct {
28+
position int
29+
rotor rotor
30+
}
31+
32+
type reflector struct {
33+
wiring []string
34+
}
35+
36+
type rotor struct {
37+
wiring []string
38+
turnovers []int
39+
}
40+
41+
const abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
42+
43+
type Config struct {
44+
RotorNames []string
45+
RingPositions []int
46+
PluboardPairings []string
47+
}
48+
49+
func NewEnigmaIEncoder(r io.Reader, cfg Config) (*Encoder, error) {
50+
e, err := newEnigmaI(cfg)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
return *Encoder{
56+
r: r,
57+
e: e,
58+
}, nil
59+
}
60+
61+
func newEnigmaI(cfg Config) (*Encoder, error) {
62+
const numberOfRotors = 3
63+
if len(cfg.RingPositions) != 3 || len(cfg.RotorNames) != 3 {
64+
return nil, errors.New("enigma 1 only has 3 rotors")
65+
}
66+
67+
// validate ring settings
68+
if ringPos1 < 0 || ringPos1 > 25 {
69+
return nil, errors.New("ring setting I must be [0,25]")
70+
}
71+
if ringPos2 < 0 || ringPos2 > 25 {
72+
return nil, errors.New("ring setting II must be [0,25]")
73+
}
74+
if ringPos3 < 0 || ringPos3 > 25 {
75+
return nil, errors.New("ring setting III must be [0,25]")
76+
}
77+
78+
// validate plugboard wirings
79+
isUppercaseAlphaPair := regexp.MustCompile(`^[A-Z][A-Z]$`).MatchString
80+
for _, p := range plugboardPairs {
81+
if !isUppercaseAlphaPair(p) || p[0] == p[1] {
82+
return nil, fmt.Errorf("plugboard pair %s is invalid", p)
83+
}
84+
// TODO: also have to make sure that a plug isn't attempted more than once
85+
// ie Q cannot be plugged into A and also into B
86+
}
87+
88+
availableReflectors := map[string]reflector{
89+
"A": {wiring: strings.Split("EJMZALYXVBWFCRQUONTSPIKHGD", "")},
90+
"B": {wiring: strings.Split("YRUHQSLDPXNGOKMIEBFZCWVJAT", "")},
91+
"C": {wiring: strings.Split("FVPJIAOYEDRZXWGCTKUQSBNMHL", "")},
92+
}
93+
94+
availableRotors := map[string]rotor{
95+
"I": rotor{
96+
wiring: strings.Split("EKMFLGDQVZNTOWYHXUSPAIBRCJ", ""),
97+
turnovers: []int{strings.Index(abc, "Q")},
98+
},
99+
"II": rotor{
100+
wiring: strings.Split("AJDKSIRUXBLHWTMCQGZNPYFVOE", ""),
101+
turnovers: []int{strings.Index(abc, "E")},
102+
},
103+
"III": rotor{
104+
wiring: strings.Split("BDFHJLCPRTXVZNYEIWGAKMUSQO", ""),
105+
turnovers: []int{strings.Index(abc, "V")},
106+
},
107+
"IV": rotor{
108+
wiring: strings.Split("ESOVPZJAYQUIRHXLNFTGKDCMWB", ""),
109+
turnovers: []int{strings.Index(abc, "J")},
110+
},
111+
"V": rotor{
112+
wiring: strings.Split("VZBRGITYUPSDNHLXAWMJQOFECK", ""),
113+
turnovers: []int{strings.Index(abc, "Z")},
114+
},
115+
"VI": rotor{
116+
wiring: strings.Split("JPGVOUMFYQBENHZRDKASXLICTW", ""),
117+
turnovers: []int{strings.Index(abc, "Z"), strings.Index(abc, "M")},
118+
},
119+
"VII": {
120+
wiring: strings.Split("NZJHGRCXMYSWBOUFAIVLPEKQDT", ""),
121+
turnovers: []int{strings.Index(abc, "Z"), strings.Index(abc, "M")},
122+
},
123+
"VIII": {
124+
wiring: strings.Split("FKQHTLXOCBJSPDZRAMEWNIUYGV", ""),
125+
turnovers: []int{strings.Index(abc, "Z"), strings.Index(abc, "M")},
126+
},
127+
}
128+
129+
// validate rotor names
130+
rot1, ok := availableRotors[rotorName1]
131+
if !ok {
132+
return nil, fmt.Errorf("rotor name: %s not found for position 1", rotorName1)
133+
}
134+
rot2, ok := availableRotors[rotorName2]
135+
if !ok {
136+
return nil, fmt.Errorf("rotor name: %s not found for position 2", rotorName2)
137+
}
138+
rot3, ok := availableRotors[rotorName3]
139+
if !ok {
140+
return nil, fmt.Errorf("rotor name: %s not found for position 3", rotorName3)
141+
}
142+
143+
// validate reflector name
144+
ref, ok := availableReflectors[reflectorName]
145+
if !ok {
146+
return nil, fmt.Errorf("reflector name: %s not found", reflectorName)
147+
}
148+
149+
var pb map[string]string
150+
for _, pp := range plugboardPairs {
151+
pb[string(pp[0])] = string(pp[1])
152+
pb[string(pp[1])] = string(pp[0])
153+
}
154+
155+
return &Enigma{
156+
w: w,
157+
r: r,
158+
rings: []ring{
159+
{rotor: rot1, position: ringPos1},
160+
{rotor: rot2, position: ringPos2},
161+
{rotor: rot3, position: ringPos3},
162+
},
163+
reflector: ref,
164+
plugboard: pb,
165+
}, nil
166+
}
167+
168+
func (e *Encoder) Encode(in string) error {
169+
if !regexp.MustCompile(`^[A-Z]*$`).MatchString(in) {
170+
return errors.New("cannot encode non-alpha text")
171+
}
172+
173+
// plugboard -> rings -> reflector -> reverse rings -> plugboard
174+
// for _, t := range text {
175+
176+
// // only run plugboard if it exists; some models don't have one
177+
// // plugIn := e.plugboard[string(t)]
178+
179+
// for _, ring := range e.rings {
180+
181+
// }
182+
183+
// // plugOut := e.plugboard[t]
184+
// }
185+
return 0, nil
186+
}

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package main
2+
3+
func main() {
4+
}

0 commit comments

Comments
 (0)