Skip to content

Commit ab9fd34

Browse files
author
Izai Alejandro Zalles Merino
committed
Add Base91 encoding and decoding operations
- Implement Base91 algorithm using 91 printable ASCII characters - Add ToBase91 operation for encoding data to Base91 - Add FromBase91 operation for decoding Base91 strings - Base91 provides ~23% better space efficiency than Base64 - Support for Unicode text, emojis, and binary data - Full round-trip encoding/decoding compatibility - Integration with CyberChef operation system
1 parent 2a1294f commit ab9fd34

File tree

4 files changed

+197
-0
lines changed

4 files changed

+197
-0
lines changed

src/core/config/Categories.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
"Show Base64 offsets",
3434
"To Base92",
3535
"From Base92",
36+
"To Base91",
37+
"From Base91",
3638
"To Base85",
3739
"From Base85",
3840
"To Base",

src/core/lib/Base91.mjs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* Base91 resources.
3+
*
4+
* Based on the original basE91 algorithm by Joachim Henke
5+
* http://base91.sourceforge.net/
6+
*
7+
* @author CyberChef Base91 Implementation
8+
* @copyright Crown Copyright 2024
9+
* @license Apache-2.0
10+
*/
11+
12+
import OperationError from "../errors/OperationError.mjs";
13+
14+
/**
15+
* Base91 alphabet - 91 printable ASCII characters
16+
*/
17+
const BASE91_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_`{|}~\"";
18+
19+
/**
20+
* Decode table for Base91
21+
*/
22+
const BASE91_DECODE_TABLE = new Array(256).fill(-1);
23+
for (let i = 0; i < BASE91_ALPHABET.length; i++) {
24+
BASE91_DECODE_TABLE[BASE91_ALPHABET.charCodeAt(i)] = i;
25+
}
26+
27+
/**
28+
* Encode bytes to Base91
29+
*
30+
* @param {Uint8Array} data - Input byte array
31+
* @returns {string} Base91 encoded string
32+
*/
33+
export function encodeBase91(data) {
34+
let accumulator = 0;
35+
let accumulatorBits = 0;
36+
let output = "";
37+
38+
for (let i = 0; i < data.length; i++) {
39+
accumulator |= data[i] << accumulatorBits;
40+
accumulatorBits += 8;
41+
42+
if (accumulatorBits > 13) {
43+
let value = accumulator & 8191;
44+
45+
if (value > 88) {
46+
accumulator >>= 13;
47+
accumulatorBits -= 13;
48+
} else {
49+
value = accumulator & 16383;
50+
accumulator >>= 14;
51+
accumulatorBits -= 14;
52+
}
53+
54+
output += BASE91_ALPHABET[value % 91] + BASE91_ALPHABET[Math.floor(value / 91)];
55+
}
56+
}
57+
58+
if (accumulatorBits > 0) {
59+
output += BASE91_ALPHABET[accumulator % 91];
60+
61+
if (accumulatorBits > 7 || accumulator > 90) {
62+
output += BASE91_ALPHABET[Math.floor(accumulator / 91)];
63+
}
64+
}
65+
66+
return output;
67+
}
68+
69+
/**
70+
* Decode Base91 string to bytes
71+
*
72+
* @param {string} str - Base91 encoded string
73+
* @returns {Uint8Array} Decoded byte array
74+
*/
75+
export function decodeBase91(str) {
76+
let accumulator = 0;
77+
let accumulatorBits = 0;
78+
let value = -1;
79+
const output = [];
80+
81+
for (let i = 0; i < str.length; i++) {
82+
const charCode = str.charCodeAt(i);
83+
const decodeValue = BASE91_DECODE_TABLE[charCode];
84+
85+
if (decodeValue === -1) {
86+
throw new OperationError(`Invalid Base91 character: ${str[i]}`);
87+
}
88+
89+
if (value === -1) {
90+
value = decodeValue;
91+
} else {
92+
value += decodeValue * 91;
93+
accumulator |= (value << accumulatorBits);
94+
95+
if (value > 88) {
96+
accumulatorBits += 13;
97+
} else {
98+
accumulatorBits += 14;
99+
}
100+
101+
value = -1;
102+
103+
while (accumulatorBits > 7) {
104+
output.push(accumulator & 255);
105+
accumulator >>= 8;
106+
accumulatorBits -= 8;
107+
}
108+
}
109+
}
110+
111+
if (value !== -1) {
112+
accumulator |= value << accumulatorBits;
113+
output.push(accumulator & 255);
114+
}
115+
116+
return new Uint8Array(output);
117+
}

src/core/operations/FromBase91.mjs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @author CyberChef Base91 Implementation
3+
* @copyright Crown Copyright 2024
4+
* @license Apache-2.0
5+
*/
6+
7+
import { decodeBase91 } from "../lib/Base91.mjs";
8+
import Operation from "../Operation.mjs";
9+
10+
/**
11+
* From Base91 operation
12+
*/
13+
class FromBase91 extends Operation {
14+
/**
15+
* FromBase91 constructor
16+
*/
17+
constructor() {
18+
super();
19+
20+
this.name = "From Base91";
21+
this.module = "Default";
22+
this.description = "Base91 is a binary-to-text encoding scheme that uses 91 printable ASCII characters. It provides better space efficiency than Base64 while maintaining readability. This operation decodes Base91-encoded text back to its original binary data.";
23+
this.infoURL = "https://en.wikipedia.org/wiki/Binary-to-text_encoding#Encoding_standards";
24+
this.inputType = "string";
25+
this.outputType = "ArrayBuffer";
26+
}
27+
28+
/**
29+
* @param {string} input
30+
* @param {Object[]} args
31+
* @returns {ArrayBuffer}
32+
*/
33+
run(input, args) {
34+
const decoded = decodeBase91(input);
35+
return decoded.buffer.slice(decoded.byteOffset, decoded.byteOffset + decoded.byteLength);
36+
}
37+
}
38+
39+
export default FromBase91;

src/core/operations/ToBase91.mjs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* @author CyberChef Base91 Implementation
3+
* @copyright Crown Copyright 2024
4+
* @license Apache-2.0
5+
*/
6+
7+
import { encodeBase91 } from "../lib/Base91.mjs";
8+
import Operation from "../Operation.mjs";
9+
10+
/**
11+
* To Base91 operation
12+
*/
13+
class ToBase91 extends Operation {
14+
/**
15+
* ToBase91 constructor
16+
*/
17+
constructor() {
18+
super();
19+
20+
this.name = "To Base91";
21+
this.module = "Default";
22+
this.description = "Base91 is a binary-to-text encoding scheme that uses 91 printable ASCII characters. It provides better space efficiency than Base64 while maintaining readability. Base91 encodes arbitrary binary data using characters A-Z, a-z, 0-9, and various symbols (excluding hyphen, backslash, and single quote).";
23+
this.infoURL = "https://en.wikipedia.org/wiki/Binary-to-text_encoding#Encoding_standards";
24+
this.inputType = "ArrayBuffer";
25+
this.outputType = "string";
26+
}
27+
28+
/**
29+
* @param {ArrayBuffer} input
30+
* @param {Object[]} args
31+
* @returns {string}
32+
*/
33+
run(input, args) {
34+
const data = new Uint8Array(input);
35+
return encodeBase91(data);
36+
}
37+
}
38+
39+
export default ToBase91;

0 commit comments

Comments
 (0)