diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index 434c8bb6..70a8796f 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -107,6 +107,8 @@
"Rabbit",
"SM4 Encrypt",
"SM4 Decrypt",
+ "RC6 Encrypt",
+ "RC6 Decrypt",
"GOST Encrypt",
"GOST Decrypt",
"GOST Sign",
diff --git a/src/core/lib/RC6.mjs b/src/core/lib/RC6.mjs
new file mode 100644
index 00000000..4dd7d7e4
--- /dev/null
+++ b/src/core/lib/RC6.mjs
@@ -0,0 +1,557 @@
+/**
+ * Complete implementation of RC6 block cipher encryption/decryption with
+ * ECB, CBC, CFB, OFB, CTR block modes.
+ *
+ * RC6 was an AES finalist designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin.
+ * Reference: https://en.wikipedia.org/wiki/RC6
+ * Test Vectors: https://datatracker.ietf.org/doc/html/draft-krovetz-rc6-rc5-vectors-00
+ *
+ * @author Medjedtxm
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import OperationError from "../errors/OperationError.mjs";
+
+/** Number of rounds (RC6-32/20/b for AES compatibility) */
+const NROUNDS = 20;
+
+/** Block size in bytes (128 bits = 4 words × 32 bits) */
+const BLOCKSIZE = 16;
+
+/** Magic constant P for w=32: Odd((e-2)*2^32) where e=2.71828... */
+const P32 = 0xB7E15163;
+
+/** Magic constant Q for w=32: Odd((phi-1)*2^32) where phi=1.61803... (golden ratio) */
+const Q32 = 0x9E3779B9;
+
+/**
+ * Rotate left 32-bit value
+ * @param {number} x - Value to rotate
+ * @param {number} n - Rotation amount (only lower 5 bits used)
+ * @returns {number} - Rotated value as unsigned 32-bit
+ */
+function ROL(x, n) {
+ n &= 0x1F; // Only use lower 5 bits (log2(32) = 5)
+ return ((x << n) | (x >>> (32 - n))) >>> 0;
+}
+
+/**
+ * Rotate right 32-bit value
+ * @param {number} x - Value to rotate
+ * @param {number} n - Rotation amount (only lower 5 bits used)
+ * @returns {number} - Rotated value as unsigned 32-bit
+ */
+function ROR(x, n) {
+ n &= 0x1F;
+ return ((x >>> n) | (x << (32 - n))) >>> 0;
+}
+
+/**
+ * Convert byte array to 32-bit word array (little-endian)
+ * @param {number[]} bytes - Input byte array
+ * @returns {number[]} - Array of 32-bit words
+ */
+function bytesToWords(bytes) {
+ const words = [];
+ for (let i = 0; i < bytes.length; i += 4) {
+ words.push(
+ ((bytes[i] || 0)) |
+ ((bytes[i + 1] || 0) << 8) |
+ ((bytes[i + 2] || 0) << 16) |
+ ((bytes[i + 3] || 0) << 24)
+ );
+ }
+ return words;
+}
+
+/**
+ * Convert 32-bit word array to byte array (little-endian)
+ * @param {number[]} words - Array of 32-bit words
+ * @returns {number[]} - Output byte array
+ */
+function wordsToBytes(words) {
+ const bytes = [];
+ for (const w of words) {
+ bytes.push(w & 0xFF);
+ bytes.push((w >>> 8) & 0xFF);
+ bytes.push((w >>> 16) & 0xFF);
+ bytes.push((w >>> 24) & 0xFF);
+ }
+ return bytes;
+}
+
+/**
+ * Generate round subkeys from user key
+ *
+ * Algorithm from RC6 specification:
+ * 1. Initialise S[0..2r+3] using P and Q
+ * 2. Mix in the user key L[0..c-1]
+ *
+ * @param {number[]} key - User key as byte array (16, 24, or 32 bytes)
+ * @returns {number[]} - Array of 2r+4 = 44 subkeys
+ */
+function generateSubkeys(key) {
+ const b = key.length; // Key length in bytes
+ const c = Math.max(Math.ceil(b / 4), 1); // Key length in words (at least 1)
+
+ // Convert key bytes to words (little-endian), pad with zeros if needed
+ const paddedKey = [...key];
+ while (paddedKey.length < c * 4) {
+ paddedKey.push(0);
+ }
+ const L = bytesToWords(paddedKey);
+
+ // Number of subkeys: 2*r + 4 = 2*20 + 4 = 44
+ const t = 2 * NROUNDS + 4;
+
+ // Initialise S array with magic constants
+ const S = new Array(t);
+ S[0] = P32;
+ for (let i = 1; i < t; i++) {
+ S[i] = (S[i - 1] + Q32) >>> 0;
+ }
+
+ // Mix key into S
+ let A = 0, B = 0;
+ let i = 0, j = 0;
+ const v = 3 * Math.max(c, t);
+
+ for (let s = 0; s < v; s++) {
+ A = S[i] = ROL((S[i] + A + B) >>> 0, 3);
+ B = L[j] = ROL((L[j] + A + B) >>> 0, (A + B) >>> 0);
+ i = (i + 1) % t;
+ j = (j + 1) % c;
+ }
+
+ return S;
+}
+
+/**
+ * Encrypt a single 128-bit block using RC6
+ *
+ * Algorithm:
+ * B = B + S[0]
+ * D = D + S[1]
+ * for i = 1 to r do
+ * t = ROL(B * (2B + 1), log2(w))
+ * u = ROL(D * (2D + 1), log2(w))
+ * A = ROL(A ^ t, u) + S[2i]
+ * C = ROL(C ^ u, t) + S[2i + 1]
+ * (A, B, C, D) = (B, C, D, A)
+ * A = A + S[2r + 2]
+ * C = C + S[2r + 3]
+ *
+ * @param {number[]} block - 16-byte plaintext block
+ * @param {number[]} S - Subkeys array
+ * @returns {number[]} - 16-byte ciphertext block
+ */
+function encryptBlock(block, S) {
+ // Convert block to 4 words (A, B, C, D) in little-endian
+ let [A, B, C, D] = bytesToWords(block);
+
+ // Pre-whitening
+ B = (B + S[0]) >>> 0;
+ D = (D + S[1]) >>> 0;
+
+ // Main rounds
+ for (let i = 1; i <= NROUNDS; i++) {
+ // t = ROL(B * (2B + 1), 5)
+ // The multiplication B * (2B + 1) needs to be done in 32-bit
+ const t = ROL(Math.imul(B, (2 * B + 1) >>> 0) >>> 0, 5);
+
+ // u = ROL(D * (2D + 1), 5)
+ const u = ROL(Math.imul(D, (2 * D + 1) >>> 0) >>> 0, 5);
+
+ // A = ROL(A ^ t, u) + S[2i]
+ A = (ROL(A ^ t, u) + S[2 * i]) >>> 0;
+
+ // C = ROL(C ^ u, t) + S[2i + 1]
+ C = (ROL(C ^ u, t) + S[2 * i + 1]) >>> 0;
+
+ // Rotate registers: (A, B, C, D) = (B, C, D, A)
+ const temp = A;
+ A = B;
+ B = C;
+ C = D;
+ D = temp;
+ }
+
+ // Post-whitening
+ A = (A + S[2 * NROUNDS + 2]) >>> 0;
+ C = (C + S[2 * NROUNDS + 3]) >>> 0;
+
+ // Convert words back to bytes
+ return wordsToBytes([A, B, C, D]);
+}
+
+/**
+ * Decrypt a single 128-bit block using RC6
+ *
+ * Algorithm (inverse of encryption):
+ * C = C - S[2r + 3]
+ * A = A - S[2r + 2]
+ * for i = r downto 1 do
+ * (A, B, C, D) = (D, A, B, C)
+ * u = ROL(D * (2D + 1), log2(w))
+ * t = ROL(B * (2B + 1), log2(w))
+ * C = ROR(C - S[2i + 1], t) ^ u
+ * A = ROR(A - S[2i], u) ^ t
+ * D = D - S[1]
+ * B = B - S[0]
+ *
+ * @param {number[]} block - 16-byte ciphertext block
+ * @param {number[]} S - Subkeys array
+ * @returns {number[]} - 16-byte plaintext block
+ */
+function decryptBlock(block, S) {
+ // Convert block to 4 words (A, B, C, D) in little-endian
+ let [A, B, C, D] = bytesToWords(block);
+
+ // Reverse post-whitening
+ C = (C - S[2 * NROUNDS + 3]) >>> 0;
+ A = (A - S[2 * NROUNDS + 2]) >>> 0;
+
+ // Main rounds in reverse
+ for (let i = NROUNDS; i >= 1; i--) {
+ // Reverse rotate registers: (A, B, C, D) = (D, A, B, C)
+ const temp = D;
+ D = C;
+ C = B;
+ B = A;
+ A = temp;
+
+ // u = ROL(D * (2D + 1), 5)
+ const u = ROL(Math.imul(D, (2 * D + 1) >>> 0) >>> 0, 5);
+
+ // t = ROL(B * (2B + 1), 5)
+ const t = ROL(Math.imul(B, (2 * B + 1) >>> 0) >>> 0, 5);
+
+ // C = ROR(C - S[2i + 1], t) ^ u
+ C = ROR((C - S[2 * i + 1]) >>> 0, t) ^ u;
+
+ // A = ROR(A - S[2i], u) ^ t
+ A = ROR((A - S[2 * i]) >>> 0, u) ^ t;
+ }
+
+ // Reverse pre-whitening
+ D = (D - S[1]) >>> 0;
+ B = (B - S[0]) >>> 0;
+
+ // Convert words back to bytes
+ return wordsToBytes([A, B, C, D]);
+}
+
+/**
+ * XOR two 16-byte blocks
+ * @param {number[]} a - First block
+ * @param {number[]} b - Second block
+ * @returns {number[]} - XOR result
+ */
+function xorBlocks(a, b) {
+ const result = new Array(BLOCKSIZE);
+ for (let i = 0; i < BLOCKSIZE; i++) {
+ result[i] = a[i] ^ b[i];
+ }
+ return result;
+}
+
+/**
+ * Increment counter (little-endian)
+ * @param {number[]} counter - 16-byte counter
+ * @returns {number[]} - Incremented counter
+ */
+function incrementCounter(counter) {
+ const result = [...counter];
+ for (let i = 0; i < BLOCKSIZE; i++) {
+ result[i]++;
+ if (result[i] <= 255) break;
+ result[i] = 0;
+ }
+ return result;
+}
+
+/**
+ * Apply padding to message
+ * @param {number[]} message - Original message
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
+ * @param {number} blockSize - Block size in bytes
+ * @returns {number[]} - Padded message
+ */
+function applyPadding(message, padding, blockSize) {
+ const remainder = message.length % blockSize;
+ let nPadding = remainder === 0 ? 0 : blockSize - remainder;
+
+ // For PKCS5, always add at least one byte (full block if already aligned)
+ if (padding === "PKCS5" && remainder === 0) {
+ nPadding = blockSize;
+ }
+
+ if (nPadding === 0) return [...message];
+
+ const paddedMessage = [...message];
+
+ switch (padding) {
+ case "NO":
+ throw new OperationError(`No padding requested but input is not a ${blockSize}-byte multiple.`);
+
+ case "PKCS5":
+ for (let i = 0; i < nPadding; i++) {
+ paddedMessage.push(nPadding);
+ }
+ break;
+
+ case "ZERO":
+ for (let i = 0; i < nPadding; i++) {
+ paddedMessage.push(0);
+ }
+ break;
+
+ case "RANDOM":
+ for (let i = 0; i < nPadding; i++) {
+ paddedMessage.push(Math.floor(Math.random() * 256));
+ }
+ break;
+
+ case "BIT":
+ paddedMessage.push(0x80);
+ for (let i = 1; i < nPadding; i++) {
+ paddedMessage.push(0);
+ }
+ break;
+
+ default:
+ throw new OperationError(`Unknown padding type: ${padding}`);
+ }
+
+ return paddedMessage;
+}
+
+/**
+ * Remove padding from message
+ * @param {number[]} message - Padded message
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
+ * @param {number} blockSize - Block size in bytes
+ * @returns {number[]} - Unpadded message
+ */
+function removePadding(message, padding, blockSize) {
+ if (message.length === 0) return message;
+
+ switch (padding) {
+ case "NO":
+ case "ZERO":
+ case "RANDOM":
+ // These padding types cannot be reliably removed
+ return message;
+
+ case "PKCS5": {
+ const padByte = message[message.length - 1];
+ if (padByte > 0 && padByte <= blockSize) {
+ // Verify padding
+ for (let i = 0; i < padByte; i++) {
+ if (message[message.length - 1 - i] !== padByte) {
+ throw new OperationError("Invalid PKCS#5 padding.");
+ }
+ }
+ return message.slice(0, message.length - padByte);
+ }
+ throw new OperationError("Invalid PKCS#5 padding.");
+ }
+
+ case "BIT": {
+ // Find 0x80 byte working backwards, skipping zeros
+ for (let i = message.length - 1; i >= 0; i--) {
+ if (message[i] === 0x80) {
+ return message.slice(0, i);
+ } else if (message[i] !== 0) {
+ throw new OperationError("Invalid BIT padding.");
+ }
+ }
+ throw new OperationError("Invalid BIT padding.");
+ }
+
+ default:
+ throw new OperationError(`Unknown padding type: ${padding}`);
+ }
+}
+
+/**
+ * Encrypt using RC6 cipher with specified block mode
+ *
+ * @param {number[]} message - Plaintext as byte array
+ * @param {number[]} key - Key (16, 24, or 32 bytes)
+ * @param {number[]} iv - IV (16 bytes, not used for ECB)
+ * @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR")
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
+ * @returns {number[]} - Ciphertext as byte array
+ */
+export function encryptRC6(message, key, iv, mode = "ECB", padding = "PKCS5") {
+ const messageLength = message.length;
+ if (messageLength === 0) return [];
+
+ const S = generateSubkeys(key);
+
+ // Apply padding for ECB/CBC modes
+ let paddedMessage;
+ if (mode === "ECB" || mode === "CBC") {
+ paddedMessage = applyPadding(message, padding, BLOCKSIZE);
+ } else {
+ // Stream modes (CFB, OFB, CTR) don't need padding
+ paddedMessage = [...message];
+ }
+
+ const cipherText = [];
+
+ switch (mode) {
+ case "ECB":
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
+ cipherText.push(...encryptBlock(block, S));
+ }
+ break;
+
+ case "CBC": {
+ let ivBlock = [...iv];
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
+ const xored = xorBlocks(block, ivBlock);
+ ivBlock = encryptBlock(xored, S);
+ cipherText.push(...ivBlock);
+ }
+ break;
+ }
+
+ case "CFB": {
+ let ivBlock = [...iv];
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
+ const encrypted = encryptBlock(ivBlock, S);
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
+ // Pad block if shorter than BLOCKSIZE
+ while (block.length < BLOCKSIZE) block.push(0);
+ ivBlock = xorBlocks(encrypted, block);
+ cipherText.push(...ivBlock);
+ }
+ return cipherText.slice(0, messageLength);
+ }
+
+ case "OFB": {
+ let ivBlock = [...iv];
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
+ ivBlock = encryptBlock(ivBlock, S);
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
+ // Pad block if shorter than BLOCKSIZE
+ while (block.length < BLOCKSIZE) block.push(0);
+ cipherText.push(...xorBlocks(ivBlock, block));
+ }
+ return cipherText.slice(0, messageLength);
+ }
+
+ case "CTR": {
+ let counter = [...iv];
+ for (let i = 0; i < paddedMessage.length; i += BLOCKSIZE) {
+ const encrypted = encryptBlock(counter, S);
+ const block = paddedMessage.slice(i, i + BLOCKSIZE);
+ // Pad block if shorter than BLOCKSIZE
+ while (block.length < BLOCKSIZE) block.push(0);
+ cipherText.push(...xorBlocks(encrypted, block));
+ counter = incrementCounter(counter);
+ }
+ return cipherText.slice(0, messageLength);
+ }
+
+ default:
+ throw new OperationError(`Invalid block cipher mode: ${mode}`);
+ }
+
+ return cipherText;
+}
+
+/**
+ * Decrypt using RC6 cipher with specified block mode
+ *
+ * @param {number[]} cipherText - Ciphertext as byte array
+ * @param {number[]} key - Key (16, 24, or 32 bytes)
+ * @param {number[]} iv - IV (16 bytes, not used for ECB)
+ * @param {string} mode - Block cipher mode ("ECB", "CBC", "CFB", "OFB", "CTR")
+ * @param {string} padding - Padding type ("NO", "PKCS5", "ZERO", "RANDOM", "BIT")
+ * @returns {number[]} - Plaintext as byte array
+ */
+export function decryptRC6(cipherText, key, iv, mode = "ECB", padding = "PKCS5") {
+ const originalLength = cipherText.length;
+ if (originalLength === 0) return [];
+
+ const S = generateSubkeys(key);
+
+ if (mode === "ECB" || mode === "CBC") {
+ if ((originalLength % BLOCKSIZE) !== 0)
+ throw new OperationError(`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of 16.`);
+ } else {
+ // Pad for stream modes
+ while ((cipherText.length % BLOCKSIZE) !== 0)
+ cipherText.push(0);
+ }
+
+ const plainText = [];
+
+ switch (mode) {
+ case "ECB":
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
+ const block = cipherText.slice(i, i + BLOCKSIZE);
+ plainText.push(...decryptBlock(block, S));
+ }
+ break;
+
+ case "CBC": {
+ let ivBlock = [...iv];
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
+ const block = cipherText.slice(i, i + BLOCKSIZE);
+ const decrypted = decryptBlock(block, S);
+ plainText.push(...xorBlocks(decrypted, ivBlock));
+ ivBlock = block;
+ }
+ break;
+ }
+
+ case "CFB": {
+ let ivBlock = [...iv];
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
+ const encrypted = encryptBlock(ivBlock, S);
+ const block = cipherText.slice(i, i + BLOCKSIZE);
+ plainText.push(...xorBlocks(encrypted, block));
+ ivBlock = block;
+ }
+ return plainText.slice(0, originalLength);
+ }
+
+ case "OFB": {
+ let ivBlock = [...iv];
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
+ ivBlock = encryptBlock(ivBlock, S);
+ const block = cipherText.slice(i, i + BLOCKSIZE);
+ plainText.push(...xorBlocks(ivBlock, block));
+ }
+ return plainText.slice(0, originalLength);
+ }
+
+ case "CTR": {
+ let counter = [...iv];
+ for (let i = 0; i < cipherText.length; i += BLOCKSIZE) {
+ const encrypted = encryptBlock(counter, S);
+ const block = cipherText.slice(i, i + BLOCKSIZE);
+ plainText.push(...xorBlocks(encrypted, block));
+ counter = incrementCounter(counter);
+ }
+ return plainText.slice(0, originalLength);
+ }
+
+ default:
+ throw new OperationError(`Invalid block cipher mode: ${mode}`);
+ }
+
+ // Remove padding for ECB/CBC modes
+ if (mode === "ECB" || mode === "CBC") {
+ return removePadding(plainText, padding, BLOCKSIZE);
+ }
+
+ return plainText.slice(0, originalLength);
+}
diff --git a/src/core/operations/RC6Decrypt.mjs b/src/core/operations/RC6Decrypt.mjs
new file mode 100644
index 00000000..ee095cb6
--- /dev/null
+++ b/src/core/operations/RC6Decrypt.mjs
@@ -0,0 +1,94 @@
+/**
+ * @author Medjedtxm
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import Utils from "../Utils.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import { toHex } from "../lib/Hex.mjs";
+import { decryptRC6 } from "../lib/RC6.mjs";
+
+/**
+ * RC6 Decrypt operation
+ */
+class RC6Decrypt extends Operation {
+
+ /**
+ * RC6Decrypt constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "RC6 Decrypt";
+ this.module = "Ciphers";
+ this.description = "RC6 is a symmetric key block cipher derived from RC5. It was designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin to meet the requirements of the AES competition, and was one of the five finalists. RC6 operates on 128-bit blocks and supports key sizes of 128, 192, or 256 bits with 20 rounds.
When using CBC or ECB mode, the PKCS#7 padding scheme is used.";
+ this.infoURL = "https://wikipedia.org/wiki/RC6";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Key",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ },
+ {
+ "name": "IV",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ },
+ {
+ "name": "Mode",
+ "type": "option",
+ "value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
+ },
+ {
+ "name": "Input",
+ "type": "option",
+ "value": ["Hex", "Raw"]
+ },
+ {
+ "name": "Output",
+ "type": "option",
+ "value": ["Raw", "Hex"]
+ },
+ {
+ "name": "Padding",
+ "type": "option",
+ "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const key = Utils.convertToByteArray(args[0].string, args[0].option),
+ iv = Utils.convertToByteArray(args[1].string, args[1].option),
+ [,, mode, inputType, outputType, padding] = args;
+
+ if (key.length !== 16 && key.length !== 24 && key.length !== 32)
+ throw new OperationError(`Invalid key length: ${key.length} bytes
+
+RC6 uses a key length of 16 bytes (128 bits), 24 bytes (192 bits), or 32 bytes (256 bits).`);
+
+ if (iv.length !== 16 && mode !== "ECB")
+ throw new OperationError(`Invalid IV length: ${iv.length} bytes
+
+RC6 uses an IV length of 16 bytes (128 bits).
+Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
+
+ input = Utils.convertToByteArray(input, inputType);
+ const output = decryptRC6(input, key, iv, mode, padding);
+ return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
+ }
+
+}
+
+export default RC6Decrypt;
diff --git a/src/core/operations/RC6Encrypt.mjs b/src/core/operations/RC6Encrypt.mjs
new file mode 100644
index 00000000..4d0b0c11
--- /dev/null
+++ b/src/core/operations/RC6Encrypt.mjs
@@ -0,0 +1,94 @@
+/**
+ * @author Medjedtxm
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import Utils from "../Utils.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import { toHex } from "../lib/Hex.mjs";
+import { encryptRC6 } from "../lib/RC6.mjs";
+
+/**
+ * RC6 Encrypt operation
+ */
+class RC6Encrypt extends Operation {
+
+ /**
+ * RC6Encrypt constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "RC6 Encrypt";
+ this.module = "Ciphers";
+ this.description = "RC6 is a symmetric key block cipher derived from RC5. It was designed by Ron Rivest, Matt Robshaw, Ray Sidney, and Yiqun Lisa Yin to meet the requirements of the AES competition, and was one of the five finalists. RC6 operates on 128-bit blocks and supports key sizes of 128, 192, or 256 bits with 20 rounds.
When using CBC or ECB mode, the PKCS#7 padding scheme is used.";
+ this.infoURL = "https://wikipedia.org/wiki/RC6";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Key",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ },
+ {
+ "name": "IV",
+ "type": "toggleString",
+ "value": "",
+ "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"]
+ },
+ {
+ "name": "Mode",
+ "type": "option",
+ "value": ["CBC", "CFB", "OFB", "CTR", "ECB"]
+ },
+ {
+ "name": "Input",
+ "type": "option",
+ "value": ["Raw", "Hex"]
+ },
+ {
+ "name": "Output",
+ "type": "option",
+ "value": ["Hex", "Raw"]
+ },
+ {
+ "name": "Padding",
+ "type": "option",
+ "value": ["PKCS5", "NO", "ZERO", "RANDOM", "BIT"]
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const key = Utils.convertToByteArray(args[0].string, args[0].option),
+ iv = Utils.convertToByteArray(args[1].string, args[1].option),
+ [,, mode, inputType, outputType, padding] = args;
+
+ if (key.length !== 16 && key.length !== 24 && key.length !== 32)
+ throw new OperationError(`Invalid key length: ${key.length} bytes
+
+RC6 uses a key length of 16 bytes (128 bits), 24 bytes (192 bits), or 32 bytes (256 bits).`);
+
+ if (iv.length !== 16 && mode !== "ECB")
+ throw new OperationError(`Invalid IV length: ${iv.length} bytes
+
+RC6 uses an IV length of 16 bytes (128 bits).
+Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
+
+ input = Utils.convertToByteArray(input, inputType);
+ const output = encryptRC6(input, key, iv, mode, padding);
+ return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
+ }
+
+}
+
+export default RC6Encrypt;
diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs
index f147e9e7..6dc0f2dc 100644
--- a/tests/operations/index.mjs
+++ b/tests/operations/index.mjs
@@ -150,6 +150,7 @@ import "./tests/Shuffle.mjs";
import "./tests/SIGABA.mjs";
import "./tests/SM2.mjs";
import "./tests/SM4.mjs";
+import "./tests/RC6.mjs";
// import "./tests/SplitColourChannels.mjs"; // Cannot test operations that use the File type yet
import "./tests/StrUtils.mjs";
import "./tests/StripIPv4Header.mjs";
diff --git a/tests/operations/tests/RC6.mjs b/tests/operations/tests/RC6.mjs
new file mode 100644
index 00000000..a70a8b75
--- /dev/null
+++ b/tests/operations/tests/RC6.mjs
@@ -0,0 +1,475 @@
+/**
+ * RC6 cipher tests.
+ *
+ * Test vectors from the IETF draft:
+ * "Test Vectors for RC6 and RC5"
+ * https://datatracker.ietf.org/doc/html/draft-krovetz-rc6-rc5-vectors-00
+ *
+ * Note: PKCS5 padding adds an extra block when input is exactly block-aligned.
+ * Round-trip tests verify correct encryption/decryption behavior.
+ *
+ * @author Medjedtxm
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ // ============================================================
+ // OFFICIAL TEST VECTORS from IETF draft-krovetz-rc6-rc5-vectors-00
+ // RC6-32/20/16 (128-bit key)
+ // ============================================================
+ {
+ name: "RC6 Official Vector 1: 128-bit key (IETF draft)",
+ input: "000102030405060708090a0b0c0d0e0f",
+ expectedOutput: "3a96f9c7f6755cfe46f00e3dcd5d2a3c",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "000102030405060708090a0b0c0d0e0f", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Hex", "NO"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Official Vector 1 Decrypt: 128-bit key",
+ input: "3a96f9c7f6755cfe46f00e3dcd5d2a3c",
+ expectedOutput: "000102030405060708090a0b0c0d0e0f",
+ recipeConfig: [
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "000102030405060708090a0b0c0d0e0f", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Hex", "NO"
+ ]
+ }
+ ]
+ },
+ // RC6-32/20/24 (192-bit key) - Self-generated, verified by independent implementations
+ {
+ name: "RC6 192-bit key encrypt",
+ input: "000102030405060708090a0b0c0d0e0f",
+ expectedOutput: "a68a14ff1342262a2bbd21f7966615eb",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Hex", "NO"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 192-bit key decrypt",
+ input: "a68a14ff1342262a2bbd21f7966615eb",
+ expectedOutput: "000102030405060708090a0b0c0d0e0f",
+ recipeConfig: [
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Hex", "NO"
+ ]
+ }
+ ]
+ },
+ // RC6-32/20/32 (256-bit key) - Self-generated, verified by independent implementations
+ {
+ name: "RC6 256-bit key encrypt",
+ input: "000102030405060708090a0b0c0d0e0f",
+ expectedOutput: "921c3ecd43d9426a90089334d67aea2e",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Hex", "NO"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 256-bit key decrypt",
+ input: "921c3ecd43d9426a90089334d67aea2e",
+ expectedOutput: "000102030405060708090a0b0c0d0e0f",
+ recipeConfig: [
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Hex", "NO"
+ ]
+ }
+ ]
+ },
+ // Zero key, zero plaintext test
+ {
+ name: "RC6 Zero Key/Plaintext: 128-bit key",
+ input: "00000000000000000000000000000000",
+ expectedOutput: "8fc3a53656b1f778c129df4e9848a41e",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00000000000000000000000000000000", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Hex", "NO"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Zero Key/Plaintext Decrypt: 128-bit key",
+ input: "8fc3a53656b1f778c129df4e9848a41e",
+ expectedOutput: "00000000000000000000000000000000",
+ recipeConfig: [
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00000000000000000000000000000000", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Hex", "NO"
+ ]
+ }
+ ]
+ },
+ // ============================================================
+ // Round-trip tests - These verify encryption and decryption work correctly
+ // ============================================================
+ {
+ name: "RC6 Round-trip: ECB 128-bit key, short message",
+ input: "Hello World!!!!",
+ expectedOutput: "Hello World!!!!",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Round-trip: CBC 128-bit key, long message",
+ input: "The quick brown fox jumps over the lazy dog",
+ expectedOutput: "The quick brown fox jumps over the lazy dog",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "aabbccddeeff00112233445566778899", option: "Hex" },
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ "CBC", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "aabbccddeeff00112233445566778899", option: "Hex" },
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ "CBC", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Round-trip: ECB 192-bit key",
+ input: "Testing RC6 cipher with 192-bit key",
+ expectedOutput: "Testing RC6 cipher with 192-bit key",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff0011223344556677", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff0011223344556677", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Round-trip: CBC 256-bit key",
+ input: "RC6 is a symmetric block cipher designed by Ron Rivest!",
+ expectedOutput: "RC6 is a symmetric block cipher designed by Ron Rivest!",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100", option: "Hex" },
+ { string: "8877665544332211ffeeddccbbaa9988", option: "Hex" },
+ "CBC", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "ffeeddccbbaa99887766554433221100ffeeddccbbaa99887766554433221100", option: "Hex" },
+ { string: "8877665544332211ffeeddccbbaa9988", option: "Hex" },
+ "CBC", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Round-trip: UTF8 key (16 bytes)",
+ input: "Secret message",
+ expectedOutput: "Secret message",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "mypassword123456", option: "UTF8" },
+ { string: "initializevec123", option: "UTF8" },
+ "CBC", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "mypassword123456", option: "UTF8" },
+ { string: "initializevec123", option: "UTF8" },
+ "CBC", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ // CFB mode tests
+ {
+ name: "RC6 Round-trip: CFB mode",
+ input: "CFB mode test message",
+ expectedOutput: "CFB mode test message",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "ffeeddccbbaa99887766554433221100", option: "Hex" },
+ "CFB", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "ffeeddccbbaa99887766554433221100", option: "Hex" },
+ "CFB", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ // OFB mode tests
+ {
+ name: "RC6 Round-trip: OFB mode",
+ input: "OFB mode test message",
+ expectedOutput: "OFB mode test message",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "aabbccddeeff00112233445566778899", option: "Hex" },
+ "OFB", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "aabbccddeeff00112233445566778899", option: "Hex" },
+ "OFB", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ // CTR mode tests
+ {
+ name: "RC6 Round-trip: CTR mode",
+ input: "CTR mode test message",
+ expectedOutput: "CTR mode test message",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "00000000000000000000000000000001", option: "Hex" },
+ "CTR", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "00000000000000000000000000000001", option: "Hex" },
+ "CTR", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ // Edge case tests - various input lengths
+ {
+ name: "RC6 Round-trip: Various lengths 1 byte",
+ input: "A",
+ expectedOutput: "A",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Round-trip: Various lengths 15 bytes",
+ input: "123456789012345",
+ expectedOutput: "123456789012345",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Round-trip: Various lengths 16 bytes (exact block)",
+ input: "1234567890123456",
+ expectedOutput: "1234567890123456",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Round-trip: Various lengths 17 bytes",
+ input: "12345678901234567",
+ expectedOutput: "12345678901234567",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Round-trip: Various lengths 32 bytes (two blocks)",
+ input: "12345678901234567890123456789012",
+ expectedOutput: "12345678901234567890123456789012",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ { string: "", option: "Hex" },
+ "ECB", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ },
+ {
+ name: "RC6 Round-trip: Binary data",
+ input: "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ expectedOutput: "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f",
+ recipeConfig: [
+ {
+ op: "RC6 Encrypt",
+ args: [
+ { string: "ffeeddccbbaa99887766554433221100", option: "Hex" },
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ "CBC", "Raw", "Hex", "PKCS5"
+ ]
+ },
+ {
+ op: "RC6 Decrypt",
+ args: [
+ { string: "ffeeddccbbaa99887766554433221100", option: "Hex" },
+ { string: "00112233445566778899aabbccddeeff", option: "Hex" },
+ "CBC", "Hex", "Raw", "PKCS5"
+ ]
+ }
+ ]
+ }
+]);