diff --git a/CHANGELOG.md b/CHANGELOG.md
index 87a81bdd..a65fc145 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,9 @@
# Changelog
All major and minor version changes will be documented in this file. Details of patch-level version changes can be found in [commit messages](https://github.com/gchq/CyberChef/commits/master).
+### [8.13.0] - 2018-12-15
+- 'A1Z26 Cipher Encode' and 'A1Z26 Cipher Decode' operations added [@jarmovanlenthe] | [#441]
+
### [8.12.0] - 2018-11-21
- 'Citrix CTX1 Encode' and 'Citrix CTX1 Decode' operations added [@bwhitn] | [#428]
@@ -20,7 +23,7 @@ All major and minor version changes will be documented in this file. Details of
- 'JWT Sign', 'JWT Verify' and 'JWT Decode' operations added [@GCHQ77703] | [#348]
### [8.6.0] - 2018-08-29
-- 'To Geohash' and 'From Geohash' operations added [@GCHQ77703] | [#344]
+- 'To Geohash' and 'From Geohash' operations added [@GCHQ77703] | [#344]
### [8.5.0] - 2018-08-23
- 'To Braille' and 'From Braille' operations added [@n1474335] | [#255]
@@ -66,6 +69,7 @@ All major and minor version changes will be documented in this file. Details of
+[8.13.0]: https://github.com/gchq/CyberChef/releases/tag/v8.13.0
[8.12.0]: https://github.com/gchq/CyberChef/releases/tag/v8.12.0
[8.11.0]: https://github.com/gchq/CyberChef/releases/tag/v8.11.0
[8.10.0]: https://github.com/gchq/CyberChef/releases/tag/v8.10.0
@@ -96,6 +100,7 @@ All major and minor version changes will be documented in this file. Details of
[@arnydo]: https://github.com/arnydo
[@klaxon1]: https://github.com/klaxon1
[@bwhitn]: https://github.com/bwhitn
+[@jarmovanlenthe]: https://github.com/jarmovanlenthe
[#95]: https://github.com/gchq/CyberChef/pull/299
[#173]: https://github.com/gchq/CyberChef/pull/173
@@ -119,3 +124,4 @@ All major and minor version changes will be documented in this file. Details of
[#387]: https://github.com/gchq/CyberChef/pull/387
[#394]: https://github.com/gchq/CyberChef/pull/394
[#428]: https://github.com/gchq/CyberChef/pull/428
+[#441]: https://github.com/gchq/CyberChef/pull/441
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 4fac84c9..e9fe3399 100755
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -86,6 +86,8 @@
"Bifid Cipher Decode",
"Affine Cipher Encode",
"Affine Cipher Decode",
+ "A1Z26 Cipher Encode",
+ "A1Z26 Cipher Decode",
"Atbash Cipher",
"Substitute",
"Derive PBKDF2 key",
diff --git a/src/core/operations/A1Z26CipherDecode.mjs b/src/core/operations/A1Z26CipherDecode.mjs
new file mode 100644
index 00000000..2a9f9ce7
--- /dev/null
+++ b/src/core/operations/A1Z26CipherDecode.mjs
@@ -0,0 +1,63 @@
+/**
+ * @author Jarmo van Lenthe [github.com/jarmovanlenthe]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+import {DELIM_OPTIONS} from "../lib/Delim";
+import OperationError from "../errors/OperationError";
+
+/**
+ * A1Z26 Cipher Decode operation
+ */
+class A1Z26CipherDecode extends Operation {
+
+ /**
+ * A1Z26CipherDecode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "A1Z26 Cipher Decode";
+ this.module = "Ciphers";
+ this.description = "Converts alphabet order numbers into their corresponding alphabet character.
e.g. 1
becomes a
and 2
becomes b
.";
+ this.infoURL = "";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Delimiter",
+ type: "option",
+ value: DELIM_OPTIONS
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const delim = Utils.charRep(args[0] || "Space");
+
+ if (input.length === 0) {
+ return [];
+ }
+
+ const bites = input.split(delim);
+ let latin1 = "";
+ for (let i = 0; i < bites.length; i++) {
+ if (bites[i] < 1 || bites[i] > 26) {
+ throw new OperationError("Error: all numbers must be between 1 and 26.");
+ }
+ latin1 += Utils.chr(parseInt(bites[i], 10) + 96);
+ }
+ return latin1;
+ }
+
+}
+
+export default A1Z26CipherDecode;
diff --git a/src/core/operations/A1Z26CipherEncode.mjs b/src/core/operations/A1Z26CipherEncode.mjs
new file mode 100644
index 00000000..d1202d83
--- /dev/null
+++ b/src/core/operations/A1Z26CipherEncode.mjs
@@ -0,0 +1,61 @@
+/**
+ * @author Jarmo van Lenthe [github.com/jarmovanlenthe]
+ * @copyright Crown Copyright 2018
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import Utils from "../Utils";
+import {DELIM_OPTIONS} from "../lib/Delim";
+
+/**
+ * A1Z26 Cipher Encode operation
+ */
+class A1Z26CipherEncode extends Operation {
+
+ /**
+ * A1Z26CipherEncode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "A1Z26 Cipher Encode";
+ this.module = "Ciphers";
+ this.description = "Converts alphabet characters into their corresponding alphabet order number.
e.g. a
becomes 1
and b
becomes 2
.
Non-alphabet characters are dropped.";
+ this.infoURL = "";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Delimiter",
+ type: "option",
+ value: DELIM_OPTIONS
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const delim = Utils.charRep(args[0] || "Space");
+ let output = "";
+
+ const sanitizedinput = input.toLowerCase(),
+ charcode = Utils.strToCharcode(sanitizedinput);
+
+ for (let i = 0; i < charcode.length; i++) {
+ const ordinal = charcode[i] - 96;
+
+ if (ordinal > 0 && ordinal <= 26) {
+ output += ordinal.toString(10) + delim;
+ }
+ }
+ return output.slice(0, -delim.length);
+ }
+
+}
+
+export default A1Z26CipherEncode;
diff --git a/test/tests/operations/Ciphers.mjs b/test/tests/operations/Ciphers.mjs
index fdc98a3a..a46f50a9 100644
--- a/test/tests/operations/Ciphers.mjs
+++ b/test/tests/operations/Ciphers.mjs
@@ -110,6 +110,39 @@ TestRegister.addTests([
}
],
},
+ {
+ name: "A1Z26 Encode: normal",
+ input: "This is the test sentence.",
+ expectedOutput: "20 8 9 19 9 19 20 8 5 20 5 19 20 19 5 14 20 5 14 3 5",
+ recipeConfig: [
+ {
+ op: "A1Z26 Cipher Encode",
+ args: ["Space"]
+ }
+ ],
+ },
+ {
+ name: "A1Z26 Decode: normal",
+ input: "20 8 9 19 9 19 20 8 5 20 5 19 20 19 5 14 20 5 14 3 5",
+ expectedOutput: "thisisthetestsentence",
+ recipeConfig: [
+ {
+ op: "A1Z26 Cipher Decode",
+ args: ["Space"]
+ }
+ ],
+ },
+ {
+ name: "A1Z26 Decode: error",
+ input: "20 8 9 27",
+ expectedOutput: "Error: all numbers must be between 1 and 26.",
+ recipeConfig: [
+ {
+ op: "A1Z26 Cipher Decode",
+ args: ["Space"]
+ }
+ ],
+ },
{
name: "Atbash: no input",
input: "",