@ondoher/enigma 0.1.7 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +454 -93
- package/bin/enigma.js +0 -0
- package/jsconfig.json +3 -0
- package/lib/enigma/Encoder.js +12 -13
- package/lib/enigma/Enigma.js +68 -50
- package/lib/enigma/EnigmaTypes.d.ts +101 -0
- package/lib/enigma/EntryDisc.js +13 -5
- package/lib/enigma/Inventory.js +26 -6
- package/lib/enigma/PlugBoard.js +20 -17
- package/lib/enigma/Reflector.js +4 -11
- package/lib/enigma/Rotor.js +5 -35
- package/lib/enigma/standardInventory.js +2 -2
- package/lib/enigma/tests/EnigmaSpec.js +0 -5
- package/lib/enigma/tests/PlugBoardSpec.js +15 -14
- package/lib/enigma/tests/RotorSpec.js +1 -1
- package/lib/generator/CodeBook.js +180 -0
- package/lib/generator/Generator.js +94 -386
- package/lib/generator/GeneratorTypes.d.ts +100 -0
- package/lib/generator/index.js +2 -0
- package/lib/utils/Random.js +236 -0
- package/package.json +10 -3
package/lib/enigma/Rotor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { STANDARD_ALPHABET } from "./consts.js";
|
|
1
2
|
import Encoder from "./Encoder.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -6,49 +7,18 @@ import Encoder from "./Encoder.js";
|
|
|
6
7
|
* values here are specified in physical space.
|
|
7
8
|
*/
|
|
8
9
|
export default class Rotor extends Encoder {
|
|
9
|
-
|
|
10
|
-
/*
|
|
11
|
-
|
|
12
|
-
- **name**
|
|
13
|
-
- **settings**
|
|
14
|
-
- _alphabet_ (optional)
|
|
15
|
-
- _map_
|
|
16
|
-
- _ringSetting_
|
|
17
|
-
- _turnovers_
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
10
|
/**
|
|
26
11
|
* This is the constructor for the rotor.
|
|
27
12
|
*
|
|
28
13
|
* @param {String} name the name of the rotor; under normal circumstances
|
|
29
14
|
* this will be the string 'rotor-' plus the standard name for the rotor,
|
|
30
15
|
* for example 'rotor-IV'
|
|
31
|
-
* @param {
|
|
16
|
+
* @param {RotorSetup} settings an object that contains the various options that
|
|
32
17
|
* define the the rotor and how it is configured.
|
|
33
|
-
* @property [alphabet] set this to a string of letters that are an
|
|
34
|
-
* alternative to the standard A-Z. Defaults to A-Z
|
|
35
|
-
* @property {String} map a string that defines the mapping between the
|
|
36
|
-
* input and output connectors. The index into the string is the input
|
|
37
|
-
* connector and the value of this string at that index is the output
|
|
38
|
-
* connector. For example 'EKMFLGDQVZNTOWYHXUSPAIBRCJ', which is the map
|
|
39
|
-
* for standard rotor I.
|
|
40
|
-
* @property {Number} [ringSetting] a number that specifies how far forward
|
|
41
|
-
* to offset the outer ring relative to the internal wiring.
|
|
42
|
-
* @property {String} turnovers a string that specifies the relative
|
|
43
|
-
* location of where on the rotor turnover will occur. The value here is the
|
|
44
|
-
* rotation value would be displayed in the window when turnover happens,
|
|
45
|
-
* expressed as a character. The standard rotors VI-VIII, available in the
|
|
46
|
-
* later model M3 had two turnover locations, M and Z. Pass an empty string
|
|
47
|
-
* when the rotor does not rotate during stepping
|
|
48
18
|
*/
|
|
49
19
|
constructor(name, settings) {
|
|
50
20
|
super(name, settings);
|
|
51
|
-
var { map, turnovers = '', ringSetting = 0} = settings
|
|
21
|
+
var { map = STANDARD_ALPHABET, turnovers = '', ringSetting = 0} = settings
|
|
52
22
|
|
|
53
23
|
this.map = [...map];
|
|
54
24
|
this.rightMap = this.makeMap(map);
|
|
@@ -105,7 +75,7 @@ export default class Rotor extends Encoder {
|
|
|
105
75
|
var result = this.normalize(output - this.offset);
|
|
106
76
|
|
|
107
77
|
this.fire(evName, this.name,
|
|
108
|
-
`${evName} ${this.name}, input: ${input}
|
|
78
|
+
`${evName} ${this.name}, input: ${input} output: ${result} relative input: ${index}, relative output: ${output} rotation: ${this.offset}`,
|
|
109
79
|
{
|
|
110
80
|
input : input,
|
|
111
81
|
output: result,
|
|
@@ -125,7 +95,7 @@ export default class Rotor extends Encoder {
|
|
|
125
95
|
*/
|
|
126
96
|
step() {
|
|
127
97
|
// Because the turnover notch is attached to the outer ring, and the
|
|
128
|
-
// logical coordinates
|
|
98
|
+
// logical coordinates are based on the wiring, the logical position of
|
|
129
99
|
// the notch for turnover needs to be adjusted to remove the ring offset.
|
|
130
100
|
this.offset = this.normalize(this.offset + 1);
|
|
131
101
|
var turnoverOffset = this.normalize(this.offset + this.ringOffset);
|
|
@@ -5,7 +5,7 @@ import inventory from'./Inventory.js';
|
|
|
5
5
|
* I, M3 and M4
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export var
|
|
8
|
+
export var entryDiscDefinitions = {
|
|
9
9
|
default: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
10
10
|
};
|
|
11
11
|
|
|
@@ -74,7 +74,7 @@ function addReflector(name) {
|
|
|
74
74
|
inventory.addReflector(name, reflectorDefinitions[name]);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
inventory.addEntryDisc('default',
|
|
77
|
+
inventory.addEntryDisc('default', entryDiscDefinitions.default);
|
|
78
78
|
|
|
79
79
|
addRotor('I');
|
|
80
80
|
addRotor('II');
|
|
@@ -6,11 +6,6 @@ import { enigmaData } from './EnigmaData.js';
|
|
|
6
6
|
|
|
7
7
|
describe('Enigma Test Cases', function() {
|
|
8
8
|
var enigma;
|
|
9
|
-
/*
|
|
10
|
-
describe('Configuration', function() {
|
|
11
|
-
|
|
12
|
-
})
|
|
13
|
-
*/
|
|
14
9
|
|
|
15
10
|
function messageLoop(messages, which, cb) {
|
|
16
11
|
return messages.find(function(message) {
|
|
@@ -2,7 +2,8 @@ import PlugBoard from "../PlugBoard.js";
|
|
|
2
2
|
import { plugBoardData } from './PlugBoardData.js';
|
|
3
3
|
|
|
4
4
|
describe('PlugBoard Test Cases', function() {
|
|
5
|
-
|
|
5
|
+
/** @type {PlugBoard} */
|
|
6
|
+
let plugBoard;
|
|
6
7
|
|
|
7
8
|
describe('Encode', function() {
|
|
8
9
|
beforeEach(function() {
|
|
@@ -13,32 +14,32 @@ describe('PlugBoard Test Cases', function() {
|
|
|
13
14
|
|
|
14
15
|
it('passes through if no plugs defined', function() {
|
|
15
16
|
plugBoard.configure()
|
|
16
|
-
|
|
17
|
+
let alphabet = [...plugBoardData.alphabet];
|
|
17
18
|
alphabet.forEach(function(letter) {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
let input = plugBoardData.alphabet.indexOf(letter);
|
|
20
|
+
let output = plugBoard.encode('right', input);
|
|
20
21
|
expect(output).toBe(input);
|
|
21
|
-
|
|
22
|
+
output = plugBoard.encode('left', input);
|
|
22
23
|
expect(output).toBe(input);
|
|
23
24
|
})
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
it('should link cabled pairs', function() {
|
|
27
|
-
plugBoard.configure(
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
plugBoard.configure(plugBoardData.plugSettings);
|
|
29
|
+
let pairs = plugBoardData.plugSettings.split(' ');
|
|
30
|
+
let alphabet = plugBoardData.alphabet;
|
|
30
31
|
pairs.forEach(function(pair) {
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
let left = alphabet.indexOf(pair[0]);
|
|
33
|
+
let right = alphabet.indexOf(pair[1]);
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
let output = plugBoard.encode('right', left);
|
|
35
36
|
expect(output).toBe(right);
|
|
36
|
-
|
|
37
|
+
output = plugBoard.encode('right', right);
|
|
37
38
|
expect(output).toBe(left);
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
output = plugBoard.encode('left', right);
|
|
40
41
|
expect(output).toBe(left);
|
|
41
|
-
|
|
42
|
+
output = plugBoard.encode('left', left);
|
|
42
43
|
expect(output).toBe(right);
|
|
43
44
|
})
|
|
44
45
|
})
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
|
|
2
|
+
import Enigma from '../enigma/Enigma.js';
|
|
3
|
+
import '../enigma/standardInventory.js';
|
|
4
|
+
import sentences from './hamlet.js'
|
|
5
|
+
import { STANDARD_ALPHABET } from '../enigma/consts.js';
|
|
6
|
+
import Random from '../utils/Random.js';
|
|
7
|
+
import Generator from './Generator.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Use this class to generate Enigma key sheets and messages using it. The
|
|
11
|
+
* procedures used were derived from the information at
|
|
12
|
+
* [Enigma Message Procedures](https://www.ciphermachinesandcryptology.com/en/enigmaproc.htm)
|
|
13
|
+
*/
|
|
14
|
+
export default class CodeBook {
|
|
15
|
+
/**
|
|
16
|
+
* Constructor for the `CodeBook` class.
|
|
17
|
+
* @param {Enigma} enigma - all encryption will be done using this configured Enigma
|
|
18
|
+
*/
|
|
19
|
+
constructor(enigma) {
|
|
20
|
+
this.enigma = enigma
|
|
21
|
+
this.indicators = {};
|
|
22
|
+
this.generator = new Generator();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Call this method to provide a new configuration to the enigma
|
|
27
|
+
*
|
|
28
|
+
* @param {SimplifiedConfiguration} config
|
|
29
|
+
*/
|
|
30
|
+
configure(config) {
|
|
31
|
+
this.enigma.configure(config);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
reset() {
|
|
35
|
+
this.indicators = {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Call this method to create a three letter string as an identifier for a
|
|
40
|
+
* a day in a key sheet
|
|
41
|
+
*
|
|
42
|
+
* @returns {String} the string
|
|
43
|
+
*/
|
|
44
|
+
makeIndicator() {
|
|
45
|
+
let letters = [...'abcdefghijklmnopqrstuvwxyzx'];
|
|
46
|
+
let indicator;
|
|
47
|
+
|
|
48
|
+
do {
|
|
49
|
+
indicator = Random.choose(3, letters).join('');
|
|
50
|
+
} while (this.indicators[indicator]);
|
|
51
|
+
|
|
52
|
+
this.indicators[indicator] = indicator;
|
|
53
|
+
|
|
54
|
+
return indicator;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Call this method to create a single days configuration for a key sheet.
|
|
59
|
+
* This an Enigma configuration plus the other metadata.
|
|
60
|
+
*
|
|
61
|
+
* @param {number} day the day of the month
|
|
62
|
+
*
|
|
63
|
+
* @returns {KeySheetLine} One line of a key sheet
|
|
64
|
+
*/
|
|
65
|
+
generateDay(day) {
|
|
66
|
+
let settings = this.generator.generateEnigmaConfiguration();
|
|
67
|
+
let indicators = [];
|
|
68
|
+
|
|
69
|
+
indicators.push(this.makeIndicator());
|
|
70
|
+
indicators.push(this.makeIndicator());
|
|
71
|
+
indicators.push(this.makeIndicator());
|
|
72
|
+
indicators.push(this.makeIndicator());
|
|
73
|
+
|
|
74
|
+
return {day, ...settings, indicators}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Call this method to construct a key sheet for the given number of days
|
|
79
|
+
*
|
|
80
|
+
* @param {Number} days the number of days on the key sheet
|
|
81
|
+
* @returns {KeySheetLine[]} the array of day objects
|
|
82
|
+
*/
|
|
83
|
+
generateKeySheet(days) {
|
|
84
|
+
let sheet = [];
|
|
85
|
+
this.indicators = {};
|
|
86
|
+
|
|
87
|
+
for(let idx = 0; idx < days; idx++) {
|
|
88
|
+
sheet.push(this.generateDay(idx))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return sheet;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Call this method to process one sub-message from a longer message. This is
|
|
96
|
+
* part of code to generate a message as the Enigma would have been used.
|
|
97
|
+
*
|
|
98
|
+
* @param {string[]} indicators the three letter code that can be sent to
|
|
99
|
+
* reference the configuration of the Enigma. These will be used to cross
|
|
100
|
+
* reference the message to the machine configuration on the key sheet
|
|
101
|
+
* @param {String} text the cleaned up string to be encoded
|
|
102
|
+
*
|
|
103
|
+
* @returns {MessagePart} the encoded message segment
|
|
104
|
+
*/
|
|
105
|
+
encodeOnePart(indicators, text) {
|
|
106
|
+
let indicator = Random.chooseOne(indicators);
|
|
107
|
+
let paddedIndicator = (Random.choose(2, [...STANDARD_ALPHABET]).join('') + indicator).toUpperCase();
|
|
108
|
+
|
|
109
|
+
let key = Random.choose(3, [...STANDARD_ALPHABET]).join('');
|
|
110
|
+
let start = Random.choose(3, [...STANDARD_ALPHABET]).join('');
|
|
111
|
+
let enc = this.enigma.encode(key, start);
|
|
112
|
+
let clear = text;
|
|
113
|
+
|
|
114
|
+
text = text.replace(/ /g, '');
|
|
115
|
+
text = this.enigma.encode(start, text);
|
|
116
|
+
text = this.generator.groupText(text);
|
|
117
|
+
text = paddedIndicator + ' ' + text
|
|
118
|
+
|
|
119
|
+
return {key, enc, start, text, clear};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Call this method to generate a full message based on the data in a key
|
|
124
|
+
* sheet. The message constructed reflects an actual message as the Enigma
|
|
125
|
+
* was really used. An individual message cannot be more than 250
|
|
126
|
+
* characters, so longer messages are broken into multiple parts, each one
|
|
127
|
+
* encoded with a unique key
|
|
128
|
+
*
|
|
129
|
+
* @param {KeySheetLine[]} sheet a key sheet generated using generateKeySheet
|
|
130
|
+
* @param {number} [dayIdx] - if provided, specifies the day of the month for the message
|
|
131
|
+
* @param {string} [text] - if provided, this is the text of the message to generate
|
|
132
|
+
* @returns {KeyBookMessage} a list of encoded sub messages
|
|
133
|
+
*/
|
|
134
|
+
generateMessage(sheet, dayIdx, text) {
|
|
135
|
+
dayIdx = dayIdx === undefined ? Random.random(sheet.length) : dayIdx;
|
|
136
|
+
let day = sheet[dayIdx]
|
|
137
|
+
let count = Random.random(3) + 2;
|
|
138
|
+
|
|
139
|
+
this.enigma.configure({plugs: day.plugs, rotors: day.rotors, ringSettings: day.ringSettings})
|
|
140
|
+
|
|
141
|
+
text = text ?? this.generator.generateSentences(count);
|
|
142
|
+
text = this.generator.cleanMessage(text);
|
|
143
|
+
let messageParts = [];
|
|
144
|
+
|
|
145
|
+
while (text.length) {
|
|
146
|
+
let segment = text.slice(0, 245);
|
|
147
|
+
if (text.length)
|
|
148
|
+
text = text.slice(246);
|
|
149
|
+
|
|
150
|
+
messageParts.push(segment);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let messages = messageParts.map((part) => {
|
|
154
|
+
return this.encodeOnePart(day.indicators, part)
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
options: this.enigma.configuration,
|
|
159
|
+
parts: messages
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Call this method to generate a given number of messages based on a
|
|
165
|
+
* generated key sheet
|
|
166
|
+
*
|
|
167
|
+
* @param {Object} sheet the generated key sheet
|
|
168
|
+
* @param {Number} count - the number of messages to generate
|
|
169
|
+
*
|
|
170
|
+
* @returns {Array} the list of generated messages
|
|
171
|
+
*/
|
|
172
|
+
generateMessages(sheet, count) {
|
|
173
|
+
let result = [];
|
|
174
|
+
for (let idx = 0; idx < count; idx++) {
|
|
175
|
+
result.push(this.generateMessage(sheet));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
}
|