Merge branch 'master' into string-integer

This commit is contained in:
GCHQDeveloper581 2026-03-08 08:52:52 +00:00 committed by GitHub
commit 96bc24f994
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 1915 additions and 1 deletions

View File

@ -110,6 +110,8 @@
"Rabbit",
"SM4 Encrypt",
"SM4 Decrypt",
"RC6 Encrypt",
"RC6 Decrypt",
"GOST Encrypt",
"GOST Decrypt",
"GOST Sign",
@ -165,7 +167,10 @@
"Typex",
"Lorenz",
"Colossus",
"SIGABA"
"SIGABA",
"Flask Session Decode",
"Flask Session Sign",
"Flask Session Verify"
]
},
{

625
src/core/lib/RC6.mjs Normal file
View File

@ -0,0 +1,625 @@
/**
* Complete implementation of RC6 block cipher encryption/decryption with
* configurable word size (w), rounds (r), and key length (b).
*
* 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
*
* The P and Q constants are derived from mathematical constants e (Euler's number) and
* φ (golden ratio) as specified in the IETF draft. Master 256-bit values are scaled to
* any word size.
*
* @author Medjedtxm
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
import OperationError from "../errors/OperationError.mjs";
/**
* Master P constant (256-bit) from IETF draft-krovetz-rc6-rc5-vectors-00
* Derived from Odd((e-2) * 2^256) where e = 2.71828...
*/
const P_256 = 0xb7e151628aed2a6abf7158809cf4f3c762e7160f38b4da56a784d9045190cfefn;
/**
* Master Q constant (256-bit) from IETF draft-krovetz-rc6-rc5-vectors-00
* Derived from Odd((φ-1) * 2^256) where φ = 1.61803... (golden ratio)
*/
const Q_256 = 0x9e3779b97f4a7c15f39cc0605cedc8341082276bf3a27251f86c6a11d0c18e95n;
/**
* Get P constant for given word size by scaling the 256-bit master constant
* @param {number} w - Word size in bits
* @returns {bigint} - P constant for word size w
*/
function getP(w) {
return (P_256 >> BigInt(256 - w)) | 1n; // Ensure odd
}
/**
* Get Q constant for given word size by scaling the 256-bit master constant
* @param {number} w - Word size in bits
* @returns {bigint} - Q constant for word size w
*/
function getQ(w) {
return (Q_256 >> BigInt(256 - w)) | 1n; // Ensure odd
}
/**
* Get block size in bytes for given word size
* Block size = 4 words = 4 * (w/8) bytes
* @param {number} w - Word size in bits
* @returns {number} - Block size in bytes
*/
export function getBlockSize(w) {
return 4 * (w / 8);
}
/**
* Get recommended number of rounds for given word size
* @param {number} w - Word size in bits
* @returns {number} - Recommended rounds
*/
export function getDefaultRounds(w) {
if (w <= 16) return 16;
if (w <= 32) return 20;
if (w <= 64) return 24;
return 28;
}
/**
* Create mask for w-bit word
* @param {number} w - Word size in bits
* @returns {bigint} - Mask with w bits set
*/
function wordMask(w) {
return (1n << BigInt(w)) - 1n;
}
/**
* Rotate left for arbitrary word size using BigInt
* Uses lower lg(w) bits of n for rotation amount (RC6 spec)
* @param {bigint} x - Value to rotate
* @param {bigint} n - Rotation amount
* @param {number} w - Word size in bits
* @param {bigint} lgMask - Mask for lower lg(w) bits
* @returns {bigint} - Rotated value
*/
function ROL(x, n, w, lgMask) {
const mask = wordMask(w);
// Mask to lg(w) bits, then mod w for non-power-of-2 word sizes
// For power-of-2, (n & lgMask) < w always, so mod w is no-op
const shift = (n & lgMask) % BigInt(w);
return ((x << shift) | (x >> (BigInt(w) - shift))) & mask;
}
/**
* Rotate right for arbitrary word size using BigInt
* Uses lower lg(w) bits of n for rotation amount (RC6 spec)
* @param {bigint} x - Value to rotate
* @param {bigint} n - Rotation amount
* @param {number} w - Word size in bits
* @param {bigint} lgMask - Mask for lower lg(w) bits
* @returns {bigint} - Rotated value
*/
function ROR(x, n, w, lgMask) {
const mask = wordMask(w);
// Mask to lg(w) bits, then mod w for non-power-of-2 word sizes
// For power-of-2, (n & lgMask) < w always, so mod w is no-op
const shift = (n & lgMask) % BigInt(w);
return ((x >> shift) | (x << (BigInt(w) - shift))) & mask;
}
/**
* Convert byte array to word array (little-endian) using BigInt
* @param {number[]} bytes - Input byte array
* @param {number} w - Word size in bits
* @returns {bigint[]} - Array of w-bit words as BigInt
*/
function bytesToWords(bytes, w) {
const bytesPerWord = w / 8;
const words = [];
for (let i = 0; i < bytes.length; i += bytesPerWord) {
let word = 0n;
for (let j = 0; j < bytesPerWord && (i + j) < bytes.length; j++) {
word |= BigInt(bytes[i + j] || 0) << BigInt(j * 8);
}
words.push(word);
}
return words;
}
/**
* Convert word array to byte array (little-endian) using BigInt
* @param {bigint[]} words - Array of words
* @param {number} w - Word size in bits
* @returns {number[]} - Output byte array
*/
function wordsToBytes(words, w) {
const bytesPerWord = w / 8;
const bytes = [];
for (const word of words) {
for (let j = 0; j < bytesPerWord; j++) {
bytes.push(Number((word >> BigInt(j * 8)) & 0xFFn));
}
}
return bytes;
}
/**
* Generate round subkeys from user key
*
* @param {number[]} key - User key as byte array
* @param {number} rounds - Number of rounds
* @param {number} w - Word size in bits
* @returns {bigint[]} - Array of 2r+4 subkeys as BigInt
*/
function generateSubkeys(key, rounds, w) {
const bytesPerWord = w / 8;
const b = key.length;
const c = Math.max(Math.ceil(b / bytesPerWord), 1);
// Convert key bytes to words, pad with zeros if needed
const paddedKey = [...key];
while (paddedKey.length < c * bytesPerWord) {
paddedKey.push(0);
}
const L = bytesToWords(paddedKey, w);
// Number of subkeys: 2*r + 4
const t = 2 * rounds + 4;
// Get P and Q for this word size
const P = getP(w);
const Q = getQ(w);
const mask = wordMask(w);
// lg(w) mask for rotation amounts (floor of log2(w), per RC6 spec)
const lgw = Math.floor(Math.log2(w));
const lgMask = (1n << BigInt(lgw)) - 1n;
// Initialise S array with magic constants
const S = new Array(t);
S[0] = P;
for (let i = 1; i < t; i++) {
S[i] = (S[i - 1] + Q) & mask;
}
// Mix key into S
let A = 0n, B = 0n;
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) & mask, 3n, w, lgMask);
B = L[j] = ROL((L[j] + A + B) & mask, A + B, w, lgMask);
i = (i + 1) % t;
j = (j + 1) % c;
}
return S;
}
/**
* Encrypt a single block using RC6
*
* @param {number[]} block - Plaintext block (4*w/8 bytes)
* @param {bigint[]} S - Subkeys array
* @param {number} rounds - Number of rounds
* @param {number} w - Word size in bits
* @returns {number[]} - Ciphertext block
*/
function encryptBlock(block, S, rounds, w) {
const mask = wordMask(w);
const lgw = BigInt(Math.floor(Math.log2(w)));
const lgMask = (1n << lgw) - 1n;
// Convert block to 4 words (A, B, C, D)
let [A, B, C, D] = bytesToWords(block, w);
// Pre-whitening
B = (B + S[0]) & mask;
D = (D + S[1]) & mask;
// Main rounds
for (let i = 1; i <= rounds; i++) {
// t = ROL(B * (2B + 1), lg(w))
const t = ROL((B * ((2n * B + 1n) & mask)) & mask, lgw, w, lgMask);
// u = ROL(D * (2D + 1), lg(w))
const u = ROL((D * ((2n * D + 1n) & mask)) & mask, lgw, w, lgMask);
// A = ROL(A ^ t, u) + S[2i]
A = (ROL(A ^ t, u, w, lgMask) + S[2 * i]) & mask;
// C = ROL(C ^ u, t) + S[2i + 1]
C = (ROL(C ^ u, t, w, lgMask) + S[2 * i + 1]) & mask;
// 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 * rounds + 2]) & mask;
C = (C + S[2 * rounds + 3]) & mask;
// Convert words back to bytes
return wordsToBytes([A, B, C, D], w);
}
/**
* Decrypt a single block using RC6
*
* @param {number[]} block - Ciphertext block (4*w/8 bytes)
* @param {bigint[]} S - Subkeys array
* @param {number} rounds - Number of rounds
* @param {number} w - Word size in bits
* @returns {number[]} - Plaintext block
*/
function decryptBlock(block, S, rounds, w) {
const mask = wordMask(w);
const lgw = BigInt(Math.floor(Math.log2(w)));
const lgMask = (1n << lgw) - 1n;
// Convert block to 4 words (A, B, C, D)
let [A, B, C, D] = bytesToWords(block, w);
// Reverse post-whitening
C = (C - S[2 * rounds + 3] + (1n << BigInt(w))) & mask;
A = (A - S[2 * rounds + 2] + (1n << BigInt(w))) & mask;
// Main rounds in reverse
for (let i = rounds; 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), lg(w))
const u = ROL((D * ((2n * D + 1n) & mask)) & mask, lgw, w, lgMask);
// t = ROL(B * (2B + 1), lg(w))
const t = ROL((B * ((2n * B + 1n) & mask)) & mask, lgw, w, lgMask);
// C = ROR(C - S[2i + 1], t) ^ u
C = ROR((C - S[2 * i + 1] + (1n << BigInt(w))) & mask, t, w, lgMask) ^ u;
// A = ROR(A - S[2i], u) ^ t
A = ROR((A - S[2 * i] + (1n << BigInt(w))) & mask, u, w, lgMask) ^ t;
}
// Reverse pre-whitening
D = (D - S[1] + (1n << BigInt(w))) & mask;
B = (B - S[0] + (1n << BigInt(w))) & mask;
// Convert words back to bytes
return wordsToBytes([A, B, C, D], w);
}
/**
* XOR two blocks
* @param {number[]} a - First block
* @param {number[]} b - Second block
* @returns {number[]} - XOR result
*/
function xorBlocks(a, b) {
const result = new Array(a.length);
for (let i = 0; i < a.length; i++) {
result[i] = a[i] ^ b[i];
}
return result;
}
/**
* Increment counter (little-endian)
* @param {number[]} counter - Counter block
* @returns {number[]} - Incremented counter
*/
function incrementCounter(counter) {
const result = [...counter];
for (let i = 0; i < result.length; 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 as byte array
* @param {number[]} iv - IV (block size 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")
* @param {number} rounds - Number of rounds (default: 20)
* @param {number} w - Word size in bits (default: 32)
* @returns {number[]} - Ciphertext as byte array
*/
export function encryptRC6(message, key, iv, mode = "ECB", padding = "PKCS5", rounds = 20, w = 32) {
const blockSize = getBlockSize(w);
const messageLength = message.length;
if (messageLength === 0) return [];
const S = generateSubkeys(key, rounds, w);
// 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, rounds, w));
}
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, rounds, w);
cipherText.push(...ivBlock);
}
break;
}
case "CFB": {
let ivBlock = [...iv];
for (let i = 0; i < paddedMessage.length; i += blockSize) {
const encrypted = encryptBlock(ivBlock, S, rounds, w);
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, rounds, w);
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, rounds, w);
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 as byte array
* @param {number[]} iv - IV (block size 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")
* @param {number} rounds - Number of rounds (default: 20)
* @param {number} w - Word size in bits (default: 32)
* @returns {number[]} - Plaintext as byte array
*/
export function decryptRC6(cipherText, key, iv, mode = "ECB", padding = "PKCS5", rounds = 20, w = 32) {
const blockSize = getBlockSize(w);
const originalLength = cipherText.length;
if (originalLength === 0) return [];
const S = generateSubkeys(key, rounds, w);
if (mode === "ECB" || mode === "CBC") {
if ((originalLength % blockSize) !== 0)
throw new OperationError(`Invalid ciphertext length: ${originalLength} bytes. Must be a multiple of ${blockSize}.`);
} 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, rounds, w));
}
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, rounds, w);
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, rounds, w);
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, rounds, w);
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, rounds, w);
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);
}

View File

@ -0,0 +1,80 @@
/**
* @author ThePlayer372-FR []
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import { fromBase64 } from "../lib/Base64.mjs";
/**
* Flask Session Decode operation
*/
class FlaskSessionDecode extends Operation {
/**
* FlaskSessionDecode constructor
*/
constructor() {
super();
this.name = "Flask Session Decode";
this.module = "Crypto";
this.description = "Decodes the payload of a Flask session cookie (itsdangerous) into JSON.";
this.inputType = "string";
this.outputType = "JSON";
this.args = [
{
name: "View TimeStamp",
type: "boolean",
value: false
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {Object[]}
*/
run(input, args) {
input = input.trim();
const parts = input.split(".");
if (parts.length !== 3) {
throw new OperationError("Invalid Flask token format. Expected payload.timestamp.signature");
}
const payloadB64 = parts[0];
const time = parts[1];
const timeB64 = time.replace(/-/g, "+").replace(/_/g, "/");
const binary = fromBase64(timeB64);
const bytes = new Uint8Array(4);
for (let i = 0; i < 4; i++) {
bytes[i] = binary.charCodeAt(i);
}
const view = new DataView(bytes.buffer);
const timestamp = view.getInt32(0, false);
const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/");
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
let payloadJson;
try {
payloadJson = fromBase64(padded);
} catch (e) {
throw new OperationError("Invalid Base64 payload");
}
try {
let data = JSON.parse(payloadJson);
if (args[0]) {
data = {payload: data, timestamp: timestamp};
}
return data;
} catch (e) {
throw new OperationError("Unable to decode JSON payload: " + e.message);
}
}
}
export default FlaskSessionDecode;

View File

@ -0,0 +1,89 @@
/**
* @author ThePlayer372-FR []
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import CryptoApi from "crypto-api/src/crypto-api.mjs";
import Utils from "../Utils.mjs";
import { toBase64 } from "../lib/Base64.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Flask Session Sign operation
*/
class FlaskSessionSign extends Operation {
/**
* FlaskSessionSign constructor
*/
constructor() {
super();
this.name = "Flask Session Sign";
this.module = "Crypto";
this.description = "Signs a JSON payload to produce a Flask session cookie (itsdangerous HMAC).";
this.inputType = "JSON";
this.outputType = "string";
this.args = [
{
name: "Key",
type: "toggleString",
value: "",
toggleValues: ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"]
},
{
name: "Salt",
type: "toggleString",
value: "cookie-session",
toggleValues: ["UTF8", "Hex", "Decimal", "Binary", "Base64", "Latin1"]
},
{
name: "Algorithm",
type: "option",
value: ["sha1", "sha256"],
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
if (!args[0].string) {
throw new OperationError("Secret key required");
}
const key = Utils.convertToByteString(args[0].string, args[0].option);
const salt = Utils.convertToByteString(args[1].string || "cookie-session", args[1].option);
const algorithm = args[2] || "sha1";
const payloadB64 = toBase64(Utils.strToByteArray(JSON.stringify(input)));
const payload = payloadB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm));
derivedKey.update(salt);
const currentTimeStamp = Math.ceil(Date.now() / 1000);
const buffer = new ArrayBuffer(4);
const view = new DataView(buffer);
view.setInt32(0, currentTimeStamp, false);
const bytes = new Uint8Array(buffer);
let binary = "";
bytes.forEach(b => binary += String.fromCharCode(b));
const timeB64 = toBase64(Utils.strToByteArray(binary));
const time = timeB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
const data = Utils.convertToByteString(payload + "." + time, "utf8");
const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm));
sign.update(data);
const signB64 = toBase64(sign.finalize());
const sign64 = signB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
return payload + "." + time + "." + sign64;
}
}
export default FlaskSessionSign;

View File

@ -0,0 +1,136 @@
/**
* @author ThePlayer372-FR []
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
import CryptoApi from "crypto-api/src/crypto-api.mjs";
import Utils from "../Utils.mjs";
import { toBase64, fromBase64 } from "../lib/Base64.mjs";
/**
* Flask Session Verify operation
*/
class FlaskSessionVerify extends Operation {
/**
* FlaskSessionVerify constructor
*/
constructor() {
super();
this.name = "Flask Session Verify";
this.module = "Crypto";
this.description = "Verifies the HMAC signature of a Flask session cookie (itsdangerous) generated.";
this.inputType = "string";
this.outputType = "JSON";
this.args = [
{
name: "Key",
type: "toggleString",
value: "",
toggleValues: ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"]
},
{
name: "Salt",
type: "toggleString",
value: "cookie-session",
toggleValues: ["UTF8", "Hex", "Decimal", "Binary", "Base64", "Latin1"]
},
{
name: "Algorithm",
type: "option",
value: ["sha1", "sha256"],
},
{
name: "View TimeStamp",
type: "boolean",
value: true
}
];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {string}
*/
run(input, args) {
if (!args[0].string) {
throw new OperationError("Secret key required");
}
const key = Utils.convertToByteString(args[0].string, args[0].option);
const salt = Utils.convertToByteString(args[1].string || "cookie-session", args[1].option);
const algorithm = args[2] || "sha1";
input = input.trim();
const parts = input.split(".");
if (parts.length !== 3) {
throw new OperationError("Invalid Flask token format. Expected payload.timestamp.signature");
}
const data = Utils.convertToByteString(parts[0] + "." + parts[1], "utf8");
const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm));
derivedKey.update(salt);
const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm));
sign.update(data);
const payloadB64 = parts[0];
const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/");
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
const time = parts[1];
const timeB64 = time.replace(/-/g, "+").replace(/_/g, "/");
const binary = fromBase64(timeB64);
const bytes = new Uint8Array(4);
for (let i = 0; i < 4; i++) {
bytes[i] = binary.charCodeAt(i);
}
const view = new DataView(bytes.buffer);
const timestamp = view.getInt32(0, false);
let payloadJson;
try {
payloadJson = fromBase64(padded);
} catch (e) {
throw new OperationError("Invalid Base64 payload");
}
const signB64 = toBase64(sign.finalize());
const sign64 = signB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
if (sign64 !== parts[2]) {
throw new OperationError("Invalid signature!");
}
try {
const decoded = JSON.parse(payloadJson);
if (!args[3]) {
return {
valid: true,
payload: decoded,
};
} else {
return {
valid: true,
payload: decoded,
timestamp: timestamp
};
}
} catch (e) {
throw new OperationError("Unable to decode JSON payload: " + e.message);
}
}
}
export default FlaskSessionVerify;

View File

@ -0,0 +1,119 @@
/**
* @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, getBlockSize, getDefaultRounds } 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.<br><br>RC6 is parameterised as RC6-w/r/b where w is word size in bits (any multiple of 8 from 8-256), r is the number of rounds (1-255), and b is the key length in bytes. The standard AES submission uses w=32, r=20. Common word sizes: 8, 16, 32 (standard), 64, 128.<br><br><b>IV:</b> The Initialisation Vector should be 4*w/8 bytes (e.g. 16 bytes for w=32). If not entered, it will default to null bytes.<br><br><b>Padding:</b> In CBC and 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"]
},
{
"name": "Word Size",
"type": "number",
"value": 32,
"min": 8,
"max": 256,
"step": 8
},
{
"name": "Rounds",
"type": "number",
"value": 20,
"min": 1,
"max": 255
}
];
}
/**
* @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, wordSize, rounds] = args;
// Validate word size
if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0)
throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`);
const blockSize = getBlockSize(wordSize);
const defaultRounds = getDefaultRounds(wordSize);
if (iv.length !== blockSize && iv.length !== 0 && mode !== "ECB")
throw new OperationError(`Invalid IV length: ${iv.length} bytes
RC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits).
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255)
throw new OperationError(`Invalid number of rounds: ${rounds}
Rounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`);
// Default IV to null bytes if empty (like AES)
const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv;
input = Utils.convertToByteArray(input, inputType);
const output = decryptRC6(input, key, actualIv, mode, padding, rounds, wordSize);
return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
}
}
export default RC6Decrypt;

View File

@ -0,0 +1,119 @@
/**
* @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, getBlockSize, getDefaultRounds } 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.<br><br>RC6 is parameterised as RC6-w/r/b where w is word size in bits (any multiple of 8 from 8-256), r is the number of rounds (1-255), and b is the key length in bytes. The standard AES submission uses w=32, r=20. Common word sizes: 8, 16, 32 (standard), 64, 128.<br><br><b>IV:</b> The Initialisation Vector should be 4*w/8 bytes (e.g. 16 bytes for w=32). If not entered, it will default to null bytes.<br><br><b>Padding:</b> In CBC and 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"]
},
{
"name": "Word Size",
"type": "number",
"value": 32,
"min": 8,
"max": 256,
"step": 8
},
{
"name": "Rounds",
"type": "number",
"value": 20,
"min": 1,
"max": 255
}
];
}
/**
* @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, wordSize, rounds] = args;
// Validate word size
if (!Number.isInteger(wordSize) || wordSize < 8 || wordSize > 256 || wordSize % 8 !== 0)
throw new OperationError(`Invalid word size: ${wordSize}. Must be a multiple of 8 between 8 and 256.`);
const blockSize = getBlockSize(wordSize);
const defaultRounds = getDefaultRounds(wordSize);
if (iv.length !== blockSize && iv.length !== 0 && mode !== "ECB")
throw new OperationError(`Invalid IV length: ${iv.length} bytes
RC6-${wordSize} uses an IV length of ${blockSize} bytes (${blockSize * 8} bits).
Make sure you have specified the type correctly (e.g. Hex vs UTF8).`);
if (!Number.isInteger(rounds) || rounds < 1 || rounds > 255)
throw new OperationError(`Invalid number of rounds: ${rounds}
Rounds must be an integer between 1 and 255. Standard for w=${wordSize} is ${defaultRounds}.`);
// Default IV to null bytes if empty (like AES)
const actualIv = iv.length === 0 ? new Array(blockSize).fill(0) : iv;
input = Utils.convertToByteArray(input, inputType);
const output = encryptRC6(input, key, actualIv, mode, padding, rounds, wordSize);
return outputType === "Hex" ? toHex(output, "") : Utils.byteArrayToUtf8(output);
}
}
export default RC6Encrypt;

View File

@ -249,6 +249,13 @@ optgroup {
}
/* Bootstrap form inside CodeMirror editor */
.cm-panel > .bmd-form-group {
padding-top: 0;
}
/* CodeMirror */
.ͼ2 .cm-specialChar,

View File

@ -152,6 +152,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/SQLBeautify.mjs";
import "./tests/StrUtils.mjs";

View File

@ -0,0 +1,246 @@
/**
* Flask Session tests
*
* @author ThePlayer372-FR []
*
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
const validTokenSha1 = "eyJyb2xlIjoic3VwZXJ1c2VyIiwidXNlciI6ImFkbWluIn0.aZ-KEw.E_x6bOhA4GU9t72pMinJUjN-O3I";
const validTokenSha256 = "eyJyb2xlIjoic3VwZXJ1c2VyIiwidXNlciI6ImFkbWluIn0.aab3Ew.Jsx2DOx_H9anZg0YcvhsASxQ11897EFHeQfS2oja4y8";
const validKey = "mysecretkey";
const wrongKey = "notTheKey";
const outputObject = {
user: "admin",
role: "superuser",
};
const outputVerify = {
valid: true,
payload: outputObject,
};
TestRegister.addTests([
{
name: "Flask Session: Decode",
input: validTokenSha1,
expectedOutput: outputObject,
recipeConfig: [
{
op: "Flask Session Decode",
args: [
false
],
}
]
},
{
name: "Flask Session: Verify Sha1",
input: validTokenSha1,
expectedOutput: outputVerify,
recipeConfig: [
{
op: "Flask Session Verify",
args: [
{
string: validKey,
option: "UTF8"
},
{
string: "cookie-session",
option: "UTF8"
},
"sha1",
false,
],
}
]
},
{
name: "Flask Session: Verify Sha256",
input: validTokenSha256,
expectedOutput: outputVerify,
recipeConfig: [
{
op: "Flask Session Verify",
args: [
{
string: validKey,
option: "UTF8"
},
{
string: "cookie-session",
option: "UTF8"
},
"sha256",
false,
],
}
]
},
{
name: "Flask Session: Sign Sha1",
input: outputObject,
expectedOutput: outputVerify,
recipeConfig: [
{
op: "Flask Session Sign",
args: [
{
string: validKey,
option: "UTF8"
},
{
string: "cookie-session",
option: "UTF8"
},
"sha1"
]
},
{
op: "Flask Session Verify",
args: [
{
string: validKey,
option: "UTF8"
},
{
string: "cookie-session",
option: "UTF8"
},
"sha1",
false,
],
}
]
},
{
name: "Flask Session: Sign Sha256",
input: outputObject,
expectedOutput: outputVerify,
recipeConfig: [
{
op: "Flask Session Sign",
args: [
{
string: validKey,
option: "UTF8"
},
{
string: "cookie-session",
option: "UTF8"
},
"sha256"
]
},
{
op: "Flask Session Verify",
args: [
{
string: validKey,
option: "UTF8"
},
{
string: "cookie-session",
option: "UTF8"
},
"sha256",
false,
],
}
]
},
{
name: "Flask Session: Verify Sha1 Wrong Key",
input: validTokenSha1,
expectedOutput: "Invalid signature!",
recipeConfig: [
{
op: "Flask Session Verify",
args: [
{
string: wrongKey,
option: "UTF8"
},
{
string: "cookie-session",
option: "UTF8"
},
"sha1",
false,
],
}
]
},
{
name: "Flask Session: Verify Sha256 Wrong Key",
input: validTokenSha256,
expectedOutput: "Invalid signature!",
recipeConfig: [
{
op: "Flask Session Verify",
args: [
{
string: wrongKey,
option: "UTF8"
},
{
string: "cookie-session",
option: "UTF8"
},
"sha256",
false,
],
}
]
},
{
name: "Flask Session: Verify Sha1 Wrong Salt",
input: validTokenSha1,
expectedOutput: "Invalid signature!",
recipeConfig: [
{
op: "Flask Session Verify",
args: [
{
string: validKey,
option: "UTF8"
},
{
string: "notTheSalt",
option: "UTF8"
},
"sha1",
false,
],
}
]
},
{
name: "Flask Session: Verify Sha256 Wrong Salt",
input: validTokenSha256,
expectedOutput: "Invalid signature!",
recipeConfig: [
{
op: "Flask Session Verify",
args: [
{
string: validKey,
option: "UTF8"
},
{
string: "notTheSalt",
option: "UTF8"
},
"sha256",
false,
],
}
]
},
]);

View File

@ -0,0 +1,487 @@
/**
* 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
*
* @author Medjedtxm
* @copyright Crown Copyright 2026
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
// ============================================================
// IETF TEST VECTORS - RC6-8/12/4
// ============================================================
{
name: "RC6-8/12/4: IETF vector encrypt",
input: "00010203",
expectedOutput: "aefc4612",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "00010203", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 8, 12
]
}
]
},
{
name: "RC6-8/12/4: IETF vector decrypt",
input: "aefc4612",
expectedOutput: "00010203",
recipeConfig: [
{
op: "RC6 Decrypt",
args: [
{ string: "00010203", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 8, 12
]
}
]
},
// ============================================================
// IETF TEST VECTORS - RC6-16/16/8
// ============================================================
{
name: "RC6-16/16/8: IETF vector encrypt",
input: "0001020304050607",
expectedOutput: "2ff0b68eaeffad5b",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "0001020304050607", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 16, 16
]
}
]
},
{
name: "RC6-16/16/8: IETF vector decrypt",
input: "2ff0b68eaeffad5b",
expectedOutput: "0001020304050607",
recipeConfig: [
{
op: "RC6 Decrypt",
args: [
{ string: "0001020304050607", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 16, 16
]
}
]
},
// ============================================================
// IETF TEST VECTORS - RC6-32/20/16 (AES standard)
// ============================================================
{
name: "RC6-32/20/16: IETF vector encrypt (AES standard)",
input: "000102030405060708090a0b0c0d0e0f",
expectedOutput: "3a96f9c7f6755cfe46f00e3dcd5d2a3c",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 32, 20
]
}
]
},
{
name: "RC6-32/20/16: IETF vector decrypt (AES standard)",
input: "3a96f9c7f6755cfe46f00e3dcd5d2a3c",
expectedOutput: "000102030405060708090a0b0c0d0e0f",
recipeConfig: [
{
op: "RC6 Decrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 32, 20
]
}
]
},
// ============================================================
// IETF TEST VECTORS - RC6-64/24/24
// ============================================================
{
name: "RC6-64/24/24: IETF vector encrypt",
input: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
expectedOutput: "c002de050bd55e5d36864ab9853338e6dc4a1326c6bdaaeb1bc9e4fd67886617",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 64, 24
]
}
]
},
{
name: "RC6-64/24/24: IETF vector decrypt",
input: "c002de050bd55e5d36864ab9853338e6dc4a1326c6bdaaeb1bc9e4fd67886617",
expectedOutput: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
recipeConfig: [
{
op: "RC6 Decrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 64, 24
]
}
]
},
// ============================================================
// IETF TEST VECTORS - RC6-128/28/32
// ============================================================
{
name: "RC6-128/28/32: IETF vector encrypt",
input: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
expectedOutput: "4ed87c64baffecd4303ee6a79aafaef575b351c024272be70a70b4a392cfc157dba52d529a79e83845bf43d67545383aed3dbf4f0d23640e44cbf6cdaa034dcb",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 128, 28
]
}
]
},
{
name: "RC6-128/28/32: IETF vector decrypt",
input: "4ed87c64baffecd4303ee6a79aafaef575b351c024272be70a70b4a392cfc157dba52d529a79e83845bf43d67545383aed3dbf4f0d23640e44cbf6cdaa034dcb",
expectedOutput: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
recipeConfig: [
{
op: "RC6 Decrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 128, 28
]
}
]
},
// ============================================================
// IETF TEST VECTORS - RC6-24/4/0 (non-power-of-2)
// ============================================================
{
name: "RC6-24/4/0: IETF non-standard vector encrypt (w=24, empty key)",
input: "000102030405060708090a0b",
expectedOutput: "0177982579be2ee3303269b9",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 24, 4
]
}
]
},
{
name: "RC6-24/4/0: IETF non-standard vector decrypt (w=24, empty key)",
input: "0177982579be2ee3303269b9",
expectedOutput: "000102030405060708090a0b",
recipeConfig: [
{
op: "RC6 Decrypt",
args: [
{ string: "", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 24, 4
]
}
]
},
// ============================================================
// IETF TEST VECTORS - RC6-80/4/12 (non-power-of-2)
// ============================================================
{
name: "RC6-80/4/12: IETF non-standard vector encrypt (w=80)",
input: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627",
expectedOutput: "26d9d6128601d06dec3817d401f1c0ff715473543875da417c2116d1e87c919a49311b00b4e17962",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "000102030405060708090a0b", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 80, 4
]
}
]
},
{
name: "RC6-80/4/12: IETF non-standard vector decrypt (w=80)",
input: "26d9d6128601d06dec3817d401f1c0ff715473543875da417c2116d1e87c919a49311b00b4e17962",
expectedOutput: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021222324252627",
recipeConfig: [
{
op: "RC6 Decrypt",
args: [
{ string: "000102030405060708090a0b", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 80, 4
]
}
]
},
// ============================================================
// ADDITIONAL KEY SIZE TESTS - RC6-32 (192-bit and 256-bit keys)
// ============================================================
{
name: "RC6-32/20/24: 192-bit key encrypt",
input: "000102030405060708090a0b0c0d0e0f",
expectedOutput: "a68a14ff1342262a2bbd21f7966615eb",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f1011121314151617", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 32, 20
]
}
]
},
{
name: "RC6-32/20/32: 256-bit key encrypt",
input: "000102030405060708090a0b0c0d0e0f",
expectedOutput: "921c3ecd43d9426a90089334d67aea2e",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Hex", "NO", 32, 20
]
}
]
},
// ============================================================
// ROUND-TRIP TESTS - One per word size to verify encrypt/decrypt
// ============================================================
{
name: "RC6-8 Round-trip: CBC mode",
input: "Hello World!",
expectedOutput: "Hello World!",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "mysecret", option: "UTF8" },
{ string: "abcd", option: "UTF8" },
"CBC", "Raw", "Hex", "PKCS5", 8, 12
]
},
{
op: "RC6 Decrypt",
args: [
{ string: "mysecret", option: "UTF8" },
{ string: "abcd", option: "UTF8" },
"CBC", "Hex", "Raw", "PKCS5", 8, 12
]
}
]
},
{
name: "RC6-16 Round-trip: CBC mode",
input: "The quick brown fox",
expectedOutput: "The quick brown fox",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "secretkey1234567", option: "UTF8" },
{ string: "initvec!", option: "UTF8" },
"CBC", "Raw", "Hex", "PKCS5", 16, 16
]
},
{
op: "RC6 Decrypt",
args: [
{ string: "secretkey1234567", option: "UTF8" },
{ string: "initvec!", option: "UTF8" },
"CBC", "Hex", "Raw", "PKCS5", 16, 16
]
}
]
},
{
name: "RC6-32 Round-trip: CBC mode",
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", 32, 20
]
},
{
op: "RC6 Decrypt",
args: [
{ string: "aabbccddeeff00112233445566778899", option: "Hex" },
{ string: "00112233445566778899aabbccddeeff", option: "Hex" },
"CBC", "Hex", "Raw", "PKCS5", 32, 20
]
}
]
},
{
name: "RC6-64 Round-trip: CBC mode",
input: "RC6 with 64-bit words is powerful!",
expectedOutput: "RC6 with 64-bit words is powerful!",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
{ string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
"CBC", "Raw", "Hex", "PKCS5", 64, 24
]
},
{
op: "RC6 Decrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
{ string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
"CBC", "Hex", "Raw", "PKCS5", 64, 24
]
}
]
},
{
name: "RC6-128 Round-trip: ECB mode",
input: "RC6 with 128-bit words provides massive block size for testing purposes!",
expectedOutput: "RC6 with 128-bit words provides massive block size for testing purposes!",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Raw", "Hex", "PKCS5", 128, 28
]
},
{
op: "RC6 Decrypt",
args: [
{ string: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Raw", "PKCS5", 128, 28
]
}
]
},
// ============================================================
// STREAM MODES TEST - Verify CFB/OFB/CTR work correctly
// ============================================================
{
name: "RC6-32 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", 32, 20
]
},
{
op: "RC6 Decrypt",
args: [
{ string: "00112233445566778899aabbccddeeff", option: "Hex" },
{ string: "00000000000000000000000000000001", option: "Hex" },
"CTR", "Hex", "Raw", "PKCS5", 32, 20
]
}
]
},
// ============================================================
// CUSTOM ROUNDS TEST - Verify non-standard round count works
// ============================================================
{
name: "RC6-32 Round-trip: Custom 8 rounds",
input: "Testing custom rounds",
expectedOutput: "Testing custom rounds",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "00112233445566778899aabbccddeeff", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Raw", "Hex", "PKCS5", 32, 8
]
},
{
op: "RC6 Decrypt",
args: [
{ string: "00112233445566778899aabbccddeeff", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Raw", "PKCS5", 32, 8
]
}
]
},
// ============================================================
// EDGE CASE TEST - Padding boundary
// ============================================================
{
name: "RC6-32 Round-trip: Exact block size input",
input: "1234567890123456",
expectedOutput: "1234567890123456",
recipeConfig: [
{
op: "RC6 Encrypt",
args: [
{ string: "00112233445566778899aabbccddeeff", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Raw", "Hex", "PKCS5", 32, 20
]
},
{
op: "RC6 Decrypt",
args: [
{ string: "00112233445566778899aabbccddeeff", option: "Hex" },
{ string: "", option: "Hex" },
"ECB", "Hex", "Raw", "PKCS5", 32, 20
]
}
]
}
]);