mirror of
https://github.com/gchq/CyberChef.git
synced 2025-04-22 15:56:16 -04:00
Bombe: add trial decryption preview
This commit is contained in:
parent
ad6e30f3d4
commit
e74c86842b
3 changed files with 79 additions and 16 deletions
|
@ -191,9 +191,6 @@ export class BombeMachine {
|
||||||
if (ciphertext.length < crib.length) {
|
if (ciphertext.length < crib.length) {
|
||||||
throw new OperationError("Crib overruns supplied ciphertext");
|
throw new OperationError("Crib overruns supplied ciphertext");
|
||||||
}
|
}
|
||||||
if (ciphertext.length > crib.length) {
|
|
||||||
throw new OperationError("Ciphertext is longer than crib");
|
|
||||||
}
|
|
||||||
if (crib.length < 2) {
|
if (crib.length < 2) {
|
||||||
// This is the absolute bare minimum to be sane, and even then it's likely too short to
|
// This is the absolute bare minimum to be sane, and even then it's likely too short to
|
||||||
// be useful
|
// be useful
|
||||||
|
@ -204,7 +201,7 @@ export class BombeMachine {
|
||||||
// A shorter crib is preferable to reduce this chance, of course
|
// A shorter crib is preferable to reduce this chance, of course
|
||||||
throw new OperationError("Crib is too long");
|
throw new OperationError("Crib is too long");
|
||||||
}
|
}
|
||||||
for (let i=0; i<ciphertext.length; i++) {
|
for (let i=0; i<crib.length; i++) {
|
||||||
if (ciphertext[i] === crib[i]) {
|
if (ciphertext[i] === crib[i]) {
|
||||||
throw new OperationError(`Invalid crib: character ${ciphertext[i]} at pos ${i} in both ciphertext and crib`);
|
throw new OperationError(`Invalid crib: character ${ciphertext[i]} at pos ${i} in both ciphertext and crib`);
|
||||||
}
|
}
|
||||||
|
@ -396,6 +393,46 @@ export class BombeMachine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single-pair steckering. Used for trial decryption rather than building a whole plugboard
|
||||||
|
* object for one pair
|
||||||
|
* @param {number[2]} stecker - Known stecker pair.
|
||||||
|
* @param {number} x - Letter to transform.
|
||||||
|
* @result number
|
||||||
|
*/
|
||||||
|
singleStecker(stecker, x) {
|
||||||
|
if (x === stecker[0]) {
|
||||||
|
return stecker[1];
|
||||||
|
}
|
||||||
|
if (x === stecker[1]) {
|
||||||
|
return stecker[0];
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trial decryption at the current setting.
|
||||||
|
* Used after we get a stop.
|
||||||
|
* This applies the detected stecker pair if we have one. It does not handle the other
|
||||||
|
* steckering or stepping (which is why we limit it to 26 characters, since it's guaranteed to
|
||||||
|
* be wrong after that anyway).
|
||||||
|
* @param {number[2]} stecker - Known stecker pair.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
tryDecrypt(stecker) {
|
||||||
|
const fastRotor = this.indicator.rotors[0];
|
||||||
|
const initialPos = fastRotor.pos;
|
||||||
|
const res = [];
|
||||||
|
// The indicator scrambler starts in the right place for the beginning of the ciphertext.
|
||||||
|
for (let i=0; i<Math.min(26, this.ciphertext.length); i++) {
|
||||||
|
const t = this.indicator.transform(this.singleStecker(stecker, a2i(this.ciphertext[i])));
|
||||||
|
res.push(i2a(this.singleStecker(stecker, t)));
|
||||||
|
fastRotor.pos = Utils.mod(fastRotor.pos + 1, 26);
|
||||||
|
}
|
||||||
|
fastRotor.pos = initialPos;
|
||||||
|
return res.join("");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Having set up the Bombe, do the actual attack run. This tries every possible rotor setting
|
* Having set up the Bombe, do the actual attack run. This tries every possible rotor setting
|
||||||
* and attempts to logically invalidate them. If it can't, it's added to the list of candidate
|
* and attempts to logically invalidate them. If it can't, it's added to the list of candidate
|
||||||
|
@ -433,19 +470,26 @@ export class BombeMachine {
|
||||||
// Our steckering hypothesis is wrong. Correct value is the un-energised wire.
|
// Our steckering hypothesis is wrong. Correct value is the un-energised wire.
|
||||||
for (let j=0; j<26; j++) {
|
for (let j=0; j<26; j++) {
|
||||||
if (!this.wires[26*this.testRegister + j]) {
|
if (!this.wires[26*this.testRegister + j]) {
|
||||||
stecker = `${i2a(this.testRegister)} <-> ${i2a(j)}`;
|
stecker = [this.testRegister, j];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (count === 1) {
|
} else if (count === 1) {
|
||||||
// This means our hypothesis for the steckering is correct.
|
// This means our hypothesis for the steckering is correct.
|
||||||
stecker = `${i2a(this.testRegister)} <-> ${i2a(this.testInput[1])}`;
|
stecker = [this.testRegister, this.testInput[1]];
|
||||||
} else {
|
} else {
|
||||||
// Unusual, probably indicative of a poor menu. I'm a little unclear on how
|
// Unusual, probably indicative of a poor menu. I'm a little unclear on how
|
||||||
// this was really handled, but we'll return it for the moment.
|
// this was really handled, but we'll return it for the moment.
|
||||||
stecker = `? (wire count: ${count})`;
|
stecker = undefined;
|
||||||
}
|
}
|
||||||
result.push([this.indicator.getPos(), stecker]);
|
const testDecrypt = this.tryDecrypt(stecker);
|
||||||
|
let steckerStr;
|
||||||
|
if (stecker !== undefined) {
|
||||||
|
steckerStr = `${i2a(stecker[0])}${i2a(stecker[1])}`;
|
||||||
|
} else {
|
||||||
|
steckerStr = `?? (wire count: ${count})`;
|
||||||
|
}
|
||||||
|
result.push([this.indicator.getPos(), steckerStr, testDecrypt]);
|
||||||
}
|
}
|
||||||
// Step all the scramblers
|
// Step all the scramblers
|
||||||
// This loop counts how many rotors have reached their starting position (meaning the
|
// This loop counts how many rotors have reached their starting position (meaning the
|
||||||
|
|
|
@ -112,7 +112,7 @@ class Bombe extends Operation {
|
||||||
// For symmetry with the Enigma op, for the input we'll just remove all invalid characters
|
// For symmetry with the Enigma op, for the input we'll just remove all invalid characters
|
||||||
input = input.replace(/[^A-Za-z]/g, "").toUpperCase();
|
input = input.replace(/[^A-Za-z]/g, "").toUpperCase();
|
||||||
crib = crib.replace(/[^A-Za-z]/g, "").toUpperCase();
|
crib = crib.replace(/[^A-Za-z]/g, "").toUpperCase();
|
||||||
const ciphertext = input.slice(offset, offset+crib.length);
|
const ciphertext = input.slice(offset);
|
||||||
const reflector = new Reflector(reflectorstr);
|
const reflector = new Reflector(reflectorstr);
|
||||||
let update;
|
let update;
|
||||||
if (ENVIRONMENT_IS_WORKER()) {
|
if (ENVIRONMENT_IS_WORKER()) {
|
||||||
|
@ -122,9 +122,9 @@ class Bombe extends Operation {
|
||||||
}
|
}
|
||||||
const bombe = new BombeMachine(rotors, reflector, ciphertext, crib, update);
|
const bombe = new BombeMachine(rotors, reflector, ciphertext, crib, update);
|
||||||
const result = bombe.run();
|
const result = bombe.run();
|
||||||
let msg = `Bombe run on menu with ${bombe.nLoops} loops (2+ desirable). Note: Rotor positions are listed left to right and start at the beginning of the crib, and ignore stepping and the ring setting. One stecker pair is determined. Results:\n`;
|
let msg = `Bombe run on menu with ${bombe.nLoops} loops (2+ desirable). Note: Rotor positions are listed left to right and start at the beginning of the crib, and ignore stepping and the ring setting. One stecker pair is determined. A decryption preview starting at the beginning of the crib and ignoring stepping is also provided. Results:\n`;
|
||||||
for (const [setting, wires] of result) {
|
for (const [setting, stecker, decrypt] of result) {
|
||||||
msg += `Stop: ${setting} (${wires})\n`;
|
msg += `Stop: ${setting} (plugboard: ${stecker}): ${decrypt}\n`;
|
||||||
}
|
}
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,10 @@ import TestRegister from "../TestRegister";
|
||||||
|
|
||||||
TestRegister.addTests([
|
TestRegister.addTests([
|
||||||
{
|
{
|
||||||
|
// Plugboard for this test is BO LC KE GA
|
||||||
name: "Bombe: 3 rotor (self-stecker)",
|
name: "Bombe: 3 rotor (self-stecker)",
|
||||||
input: "BBYFLTHHYIJQAYBBYS",
|
input: "BBYFLTHHYIJQAYBBYS",
|
||||||
expectedMatch: /LGA \(S <-> S\)/,
|
expectedMatch: /LGA \(plugboard: SS\): VFISUSGTKSTMPSUNAK/,
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Bombe",
|
"op": "Bombe",
|
||||||
|
@ -28,7 +29,7 @@ TestRegister.addTests([
|
||||||
{
|
{
|
||||||
name: "Bombe: 3 rotor (other stecker)",
|
name: "Bombe: 3 rotor (other stecker)",
|
||||||
input: "JBYALIHDYNUAAVKBYM",
|
input: "JBYALIHDYNUAAVKBYM",
|
||||||
expectedMatch: /LGA \(A <-> G\)/,
|
expectedMatch: /LGA \(plugboard: AG\): QFIMUMAFKMQSKMYNGW/,
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Bombe",
|
"op": "Bombe",
|
||||||
|
@ -46,7 +47,7 @@ TestRegister.addTests([
|
||||||
{
|
{
|
||||||
name: "Bombe: crib offset",
|
name: "Bombe: crib offset",
|
||||||
input: "AAABBYFLTHHYIJQAYBBYS", // first three chars here are faked
|
input: "AAABBYFLTHHYIJQAYBBYS", // first three chars here are faked
|
||||||
expectedMatch: /LGA \(S <-> S\)/,
|
expectedMatch: /LGA \(plugboard: SS\): VFISUSGTKSTMPSUNAK/,
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Bombe",
|
"op": "Bombe",
|
||||||
|
@ -61,12 +62,30 @@ TestRegister.addTests([
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Bombe: multiple stops",
|
||||||
|
input: "BBYFLTHHYIJQAYBBYS",
|
||||||
|
expectedMatch: /LGA \(plugboard: TT\): VFISUSGTKSTMPSUNAK/,
|
||||||
|
recipeConfig: [
|
||||||
|
{
|
||||||
|
"op": "Bombe",
|
||||||
|
"args": [
|
||||||
|
"BDFHJLCPRTXVZNYEIWGAKMUSQO<W", // III
|
||||||
|
"AJDKSIRUXBLHWTMCQGZNPYFVOE<F", // II
|
||||||
|
"EKMFLGDQVZNTOWYHXUSPAIBRCJ<R", // I
|
||||||
|
"",
|
||||||
|
"AY BR CU DH EQ FS GL IP JX KN MO TZ VW", // B
|
||||||
|
"THISISATESTM", 0,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
/*
|
/*
|
||||||
* Long test is long
|
* Long test is long
|
||||||
{
|
{
|
||||||
name: "Bombe: 4 rotor",
|
name: "Bombe: 4 rotor",
|
||||||
input: "LUOXGJSHGEDSRDOQQX",
|
input: "LUOXGJSHGEDSRDOQQX",
|
||||||
expectedMatch: /LHSC \(S <-> S\)/,
|
expectedMatch: /LHSC \(plugboard: SS\)/,
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
"op": "Bombe",
|
"op": "Bombe",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue