diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 3a5eb0d5..70caf2eb 100755
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -29,6 +29,8 @@
"Show Base64 offsets",
"To Base85",
"From Base85",
+ "To Base94",
+ "From Base94",
"To Base",
"From Base",
"To BCD",
diff --git a/src/core/lib/Base94.mjs b/src/core/lib/Base94.mjs
new file mode 100644
index 00000000..88ad16a5
--- /dev/null
+++ b/src/core/lib/Base94.mjs
@@ -0,0 +1,178 @@
+/**
+ * Base94 functions.
+ *
+ * @author sganson@trustedsecurity.com]
+ * @license Apache-2.0
+ */
+
+import Utils from "../Utils.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+/**
+ * Base94's the input byte array, returning a string.
+ * Every four bytes of input are converted to five bytes of
+ * Base94 encoded output.
+ *
+ * @param {ArrayBuffer} data
+ * @param {boolean} [strictLength="true"]
+ * @returns {string}
+ *
+ * @example
+ * // returns "@Z<[+/- >5$@3z&T!Qh*|F.q+ZWIzJ<[+][[4+trr# "
+ * // toBase94([48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]);
+ * // e.g. toBase94(ToHex("Hello World!"))
+ */
+export function toBase94(data, strictLength=true) {
+
+ if (!data) return "";
+
+ if (data instanceof ArrayBuffer) {
+ data = new Uint8Array(data);
+ }
+ else
+ {
+ throw new OperationError(`Invalid - Input not instanceof ArrayBuffer.`);
+ }
+
+ let dataModLen = data.length % 4;
+
+ if (dataModLen > 0 && strictLength)
+ {
+
+ throw new OperationError(`Invalid - Input byte length must be a multiple of 4.`);
+
+ }
+
+ let output = "", i = 0, j = 0, acc = 0;
+
+ let dataPad = new Uint8Array(data.length + (dataModLen > 0 ? (4 - dataModLen) : 0));
+
+ dataPad.set(data,0);
+
+ while (i < dataPad.length) {
+
+ acc = 0;
+
+ for(j = 0; j < 4; j++)
+ {
+
+ acc *= 256;
+
+ acc += dataPad[i + (3 - j)];
+
+ }
+
+ for(j = 0; j < 5; j++)
+ {
+
+ output += String.fromCharCode((acc % 94)+32);
+
+ acc = Math.floor(acc / 94);
+
+ }
+
+ i += 4;
+
+ }
+
+ return output;
+
+}
+
+
+/**
+ * Un-Base94's the input string, returning a byte array.
+ * Every five bytes of Base94 encoded input are converted to
+ * four bytes of output.
+ *
+ * @param {string} data // Base94 encoded string
+ * @param {boolean} [strictLength="true"]
+ * @param {boolean} [removeInvalidChars="false"]
+ * @returns {byteArray}
+ *
+ * @example
+ * // returns [48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]
+ * // fromBase94("@Z<[+/- >5$@3z&T!Qh*|F.q+ZWIzJ<[+][[4+trr# ", true, true);
+ * // e.g. fromHex(fromBase94(....)); -> Hello World!
+ */
+export function fromBase94(data, strictLength=true, removeInvalidChars=false) {
+
+ if (!data) {
+ return [];
+ }
+
+ if (typeof data == "string") {
+
+ data = Utils.strToByteArray(data);
+
+ }
+ else {
+
+ throw new OperationError(`Invalid - typeof base94 input is not a string.`);
+
+ }
+
+ const re = new RegExp("[^\x20-\x7e]", "g");
+
+ if(re.test(data))
+ {
+ if (removeInvalidChars) {
+ data = data.replace(re, "");
+ }
+ else {
+ throw new OperationError(`Invalid content in Base94 string.`);
+ }
+ }
+
+ let stringModLen = data.length % 5;
+
+ if (stringModLen > 0)
+ {
+
+ if(strictLength)
+ {
+ throw new OperationError(`Invalid - Input string length must be a multiple of 5.`);
+ }
+
+ stringModLen = 5 - stringModLen;
+
+ while(stringModLen > 0)
+ {
+
+ data.push(32);
+
+ stringModLen -= 1;
+
+ }
+
+ }
+
+ let output = [], i = 0, j = 0, acc = 0;
+
+ while (i < data.length) {
+
+ acc = 0;
+
+ for (j = 0; j < 5; j++)
+ {
+
+ acc = (acc * 94) + data[i + 4 - j] - 32;
+
+ }
+
+ for (j = 0; j < 4; j++)
+ {
+
+ output.push(acc % 256);
+
+ acc = Math.floor(acc / 256);
+
+ }
+
+ i += 5;
+
+ }
+
+ return output;
+
+}
diff --git a/src/core/operations/FromBase94.mjs b/src/core/operations/FromBase94.mjs
new file mode 100644
index 00000000..634a6ddd
--- /dev/null
+++ b/src/core/operations/FromBase94.mjs
@@ -0,0 +1,84 @@
+/**
+ * @author sganson@trustedsecurity.com]
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import {fromBase94} from "../lib/Base94.mjs";
+
+/**
+ * From Base94 operation
+ */
+class FromBase94 extends Operation {
+
+ /**
+ * FromBase94 constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "From Base94";
+ this.module = "Default";
+ this.description = "Base94 is a notation for encoding arbitrary byte data using a restricted set of symbols and is found primarily in the finance/ATM technology space.
This operation decodes an ASCII Base94 string returning a byteArray.
e.g. @Z<[+/- >5$@3z&T!Qh*|F.q+ZWIzJ<[+][[4+trr#
becomes [48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]
This is a no frills, no soft toilet paper implementation. It's string in, byteArray out.
By default, input length is expected to by a multiple of 5. Unchecking 'Strict length' will pad non mod 5 length input with space(s).
Base94 encoded content is expected to be in ASCII range '0x20 thru 0x7e'. Leaving 'Remove Invalid Chars' unchecked will enforce this.";
+ this.inputType = "string";
+ this.outputType = "byteArray";
+ this.args = [
+ {
+ name: "Strict length",
+ type: "boolean",
+ value: true
+ },
+ {
+ name: "Remove Invalid Chars",
+ type: "boolean",
+ value: false
+ }
+ ];
+
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {byteArray}
+ */
+ run(input, args) {
+
+ const [strictLength,removeInvalidChars] = args;
+
+ return fromBase94(input, strictLength, removeInvalidChars);
+
+ }
+
+ /**
+ * Highlight to Base94
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ pos[0].start = Math.ceil(pos[0].start / 4 * 5);
+ pos[0].end = Math.floor(pos[0].end / 4 * 5);
+ return pos;
+ }
+
+ /**
+ * Highlight from Base94
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ pos[0].start = Math.floor(pos[0].start / 5 * 4);
+ pos[0].end = Math.ceil(pos[0].end / 5 * 4);
+ return pos;
+ }
+}
+
+export default FromBase94;
diff --git a/src/core/operations/ToBase94.mjs b/src/core/operations/ToBase94.mjs
new file mode 100644
index 00000000..cf299158
--- /dev/null
+++ b/src/core/operations/ToBase94.mjs
@@ -0,0 +1,76 @@
+/**
+ * @author sganson@trustedsecurity.com]
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import {toBase94} from "../lib/Base94.mjs";
+
+/**
+ * To Base64 operation
+ */
+class ToBase94 extends Operation {
+
+ /**
+ * ToBase94 constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "To Base94";
+ this.module = "Default";
+ this.description = "Base94 is a notation for encoding arbitrary byte data using a restricted set of symbols and is found primarily in the finance/ATM technology space.
This operation encodes raw data into an ASCII Base94 string.
e.g. [48, 65, 6c, 6c, 6f, 20, 57, 6f, 72, 6c, 64, 21]
becomes @Z<[+/- >5$@3z&T!Qh*|F.q+ZWIzJ<[+][[4+trr#
This is a no frills, no soft toilet paper implementation. It's ArrayBuffer in, string out.
By default, input length is expected to by a multiple of 4. Unchecking 'Strict length' will pad non mod 4 length input with zero(es).";
+ this.inputType = "ArrayBuffer";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Strict length",
+ type: "boolean",
+ value: true
+ }
+ ];
+
+ }
+
+ /**
+ * @param {ArrayBuffer} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [strictLength] = args;
+ return toBase94(input,strictLength);
+ }
+
+ /**
+ * Highlight to Base94
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlight(pos, args) {
+ pos[0].start = Math.floor(pos[0].start / 4 * 5);
+ pos[0].end = Math.ceil(pos[0].end / 4 * 5);
+ return pos;
+ }
+
+ /**
+ * Highlight from Base94
+ *
+ * @param {Object[]} pos
+ * @param {number} pos[].start
+ * @param {number} pos[].end
+ * @param {Object[]} args
+ * @returns {Object[]} pos
+ */
+ highlightReverse(pos, args) {
+ pos[0].start = Math.ceil(pos[0].start / 5 * 4);
+ pos[0].end = Math.floor(pos[0].end / 5 * 4);
+ return pos;
+ }
+}
+
+export default ToBase94;