Merge branch 'rabbit-stream-cipher' of https://github.com/mikecat/CyberChef

This commit is contained in:
n1474335 2022-11-25 16:36:42 +00:00
commit 1cc8b150f3
4 changed files with 427 additions and 1 deletions

View File

@ -134,7 +134,8 @@
"Typex",
"Lorenz",
"Colossus",
"SIGABA"
"SIGABA",
"Rabbit Stream Cipher"
]
},
{

View File

@ -0,0 +1,247 @@
/**
* @author mikecat
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import Operation from "../Operation.mjs";
import Utils from "../Utils.mjs";
import { toHexFast } from "../lib/Hex.mjs";
import OperationError from "../errors/OperationError.mjs";
/**
* Rabbit Stream Cipher operation
*/
class RabbitStreamCipher extends Operation {
/**
* RabbitStreamCipher constructor
*/
constructor() {
super();
this.name = "Rabbit Stream Cipher";
this.module = "Ciphers";
this.description = "Rabbit Stream Cipher, a stream cipher algorithm defined in RFC4503.<br><br>The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).<br><br>big-endian: based on RFC4503 and RFC3447<br>little-endian: compatible with Crypto++";
this.infoURL = "https://wikipedia.org/wiki/Rabbit_(cipher)";
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": "Endianness",
"type": "option",
"value": ["Big", "Little"]
},
{
"name": "Input",
"type": "option",
"value": ["Raw", "Hex"]
},
{
"name": "Output",
"type": "option",
"value": ["Raw", "Hex"]
}
];
}
/**
* @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),
endianness = args[2],
inputType = args[3],
outputType = args[4];
const littleEndian = endianness === "Little";
if (key.length !== 16) {
throw new OperationError(`Invalid key length: ${key.length} bytes (expected: 16)`);
}
if (iv.length !== 0 && iv.length !== 8) {
throw new OperationError(`Invalid IV length: ${iv.length} bytes (expected: 0 or 8)`);
}
// Inner State
const X = new Uint32Array(8), C = new Uint32Array(8);
let b = 0;
// Counter System
const A = [
0x4d34d34d, 0xd34d34d3, 0x34d34d34, 0x4d34d34d,
0xd34d34d3, 0x34d34d34, 0x4d34d34d, 0xd34d34d3
];
const counterUpdate = function() {
for (let j = 0; j < 8; j++) {
const temp = C[j] + A[j] + b;
b = (temp / ((1 << 30) * 4)) >>> 0;
C[j] = temp;
}
};
// Next-State Function
const g = function(u, v) {
const uv = (u + v) >>> 0;
const upper = uv >>> 16, lower = uv & 0xffff;
const upperUpper = upper * upper;
const upperLower2 = 2 * upper * lower;
const lowerLower = lower * lower;
const mswTemp = upperUpper + ((upperLower2 / (1 << 16)) >>> 0);
const lswTemp = lowerLower + (upperLower2 & 0xffff) * (1 << 16);
const msw = mswTemp + ((lswTemp / ((1 << 30) * 4)) >>> 0);
const lsw = lswTemp >>> 0;
return lsw ^ msw;
};
const leftRotate = function(value, width) {
return (value << width) | (value >>> (32 - width));
};
const nextStateHelper1 = function(v0, v1, v2) {
return v0 + leftRotate(v1, 16) + leftRotate(v2, 16);
};
const nextStateHelper2 = function(v0, v1, v2) {
return v0 + leftRotate(v1, 8) + v2;
};
const G = new Uint32Array(8);
const nextState = function() {
for (let j = 0; j < 8; j++) {
G[j] = g(X[j], C[j]);
}
X[0] = nextStateHelper1(G[0], G[7], G[6]);
X[1] = nextStateHelper2(G[1], G[0], G[7]);
X[2] = nextStateHelper1(G[2], G[1], G[0]);
X[3] = nextStateHelper2(G[3], G[2], G[1]);
X[4] = nextStateHelper1(G[4], G[3], G[2]);
X[5] = nextStateHelper2(G[5], G[4], G[3]);
X[6] = nextStateHelper1(G[6], G[5], G[4]);
X[7] = nextStateHelper2(G[7], G[6], G[5]);
};
// Key Setup Scheme
const K = new Uint16Array(8);
if (littleEndian) {
for (let i = 0; i < 8; i++) {
K[i] = (key[1 + 2 * i] << 8) | key[2 * i];
}
} else {
for (let i = 0; i < 8; i++) {
K[i] = (key[14 - 2 * i] << 8) | key[15 - 2 * i];
}
}
for (let j = 0; j < 8; j++) {
if (j % 2 === 0) {
X[j] = (K[(j + 1) % 8] << 16) | K[j];
C[j] = (K[(j + 4) % 8] << 16) | K[(j + 5) % 8];
} else {
X[j] = (K[(j + 5) % 8] << 16) | K[(j + 4) % 8];
C[j] = (K[j] << 16) | K[(j + 1) % 8];
}
}
for (let i = 0; i < 4; i++) {
counterUpdate();
nextState();
}
for (let j = 0; j < 8; j++) {
C[j] = C[j] ^ X[(j + 4) % 8];
}
// IV Setup Scheme
if (iv.length === 8) {
const getIVValue = function(a, b, c, d) {
if (littleEndian) {
return (iv[a] << 24) | (iv[b] << 16) |
(iv[c] << 8) | iv[d];
} else {
return (iv[7 - a] << 24) | (iv[7 - b] << 16) |
(iv[7 - c] << 8) | iv[7 - d];
}
};
C[0] = C[0] ^ getIVValue(3, 2, 1, 0);
C[1] = C[1] ^ getIVValue(7, 6, 3, 2);
C[2] = C[2] ^ getIVValue(7, 6, 5, 4);
C[3] = C[3] ^ getIVValue(5, 4, 1, 0);
C[4] = C[4] ^ getIVValue(3, 2, 1, 0);
C[5] = C[5] ^ getIVValue(7, 6, 3, 2);
C[6] = C[6] ^ getIVValue(7, 6, 5, 4);
C[7] = C[7] ^ getIVValue(5, 4, 1, 0);
for (let i = 0; i < 4; i++) {
counterUpdate();
nextState();
}
}
// Extraction Scheme
const S = new Uint8Array(16);
const extract = function() {
let pos = 0;
const addPart = function(value) {
S[pos++] = value >>> 8;
S[pos++] = value & 0xff;
};
counterUpdate();
nextState();
addPart((X[6] >>> 16) ^ (X[1] & 0xffff));
addPart((X[6] & 0xffff) ^ (X[3] >>> 16));
addPart((X[4] >>> 16) ^ (X[7] & 0xffff));
addPart((X[4] & 0xffff) ^ (X[1] >>> 16));
addPart((X[2] >>> 16) ^ (X[5] & 0xffff));
addPart((X[2] & 0xffff) ^ (X[7] >>> 16));
addPart((X[0] >>> 16) ^ (X[3] & 0xffff));
addPart((X[0] & 0xffff) ^ (X[5] >>> 16));
if (littleEndian) {
for (let i = 0, j = S.length - 1; i < j;) {
const temp = S[i];
S[i] = S[j];
S[j] = temp;
i++;
j--;
}
}
};
const data = Utils.convertToByteString(input, inputType);
const result = new Uint8Array(data.length);
for (let i = 0; i <= data.length - 16; i += 16) {
extract();
for (let j = 0; j < 16; j++) {
result[i + j] = data.charCodeAt(i + j) ^ S[j];
}
}
if (data.length % 16 !== 0) {
const offset = data.length - data.length % 16;
const length = data.length - offset;
extract();
if (littleEndian) {
for (let j = 0; j < length; j++) {
result[offset + j] = data.charCodeAt(offset + j) ^ S[j];
}
} else {
for (let j = 0; j < length; j++) {
result[offset + j] = data.charCodeAt(offset + j) ^ S[16 - length + j];
}
}
}
if (outputType === "Hex") {
return toHexFast(result);
}
return Utils.byteArrayToChars(result);
}
}
export default RabbitStreamCipher;

View File

@ -129,6 +129,7 @@ import "./tests/Shuffle.mjs";
import "./tests/FletcherChecksum.mjs";
import "./tests/CMAC.mjs";
import "./tests/AESKeyWrap.mjs";
import "./tests/RabbitStreamCipher.mjs";
// Cannot test operations that use the File type yet
// import "./tests/SplitColourChannels.mjs";

View File

@ -0,0 +1,177 @@
/**
* @author mikecat
* @copyright Crown Copyright 2022
* @license Apache-2.0
*/
import TestRegister from "../../lib/TestRegister.mjs";
TestRegister.addTests([
{
name: "Rabbit Stream Cipher: RFC Test vector, without IV 1",
input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
expectedOutput: "b15754f036a5d6ecf56b45261c4af70288e8d815c59c0c397b696c4789c68aa7f416a1c3700cd451da68d1881673d696",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "00000000000000000000000000000000"},
{"option": "Hex", "string": ""},
"Big", "Hex", "Hex"
]
}
]
},
{
name: "Rabbit Stream Cipher: RFC Test vector, without IV 2",
input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
expectedOutput: "3d2df3c83ef627a1e97fc38487e2519cf576cd61f4405b8896bf53aa8554fc19e5547473fbdb43508ae53b20204d4c5e",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "912813292e3d36fe3bfc62f1dc51c3ac"},
{"option": "Hex", "string": ""},
"Big", "Hex", "Hex"
]
}
]
},
{
name: "Rabbit Stream Cipher: RFC Test vector, without IV 3",
input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
expectedOutput: "0cb10dcda041cdac32eb5cfd02d0609b95fc9fca0f17015a7b7092114cff3ead9649e5de8bfc7f3f924147ad3a947428",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "8395741587e0c733e9e9ab01c09b0043"},
{"option": "Hex", "string": ""},
"Big", "Hex", "Hex"
]
}
]
},
{
name: "Rabbit Stream Cipher: RFC Test vector, with IV 1",
input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
expectedOutput: "c6a7275ef85495d87ccd5d376705b7ed5f29a6ac04f5efd47b8f293270dc4a8d2ade822b29de6c1ee52bdb8a47bf8f66",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "00000000000000000000000000000000"},
{"option": "Hex", "string": "0000000000000000"},
"Big", "Hex", "Hex"
]
}
]
},
{
name: "Rabbit Stream Cipher: RFC Test vector, with IV 2",
input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
expectedOutput: "1fcd4eb9580012e2e0dccc9222017d6da75f4e10d12125017b2499ffed936f2eebc112c393e738392356bdd012029ba7",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "00000000000000000000000000000000"},
{"option": "Hex", "string": "c373f575c1267e59"},
"Big", "Hex", "Hex"
]
}
]
},
{
name: "Rabbit Stream Cipher: RFC Test vector, with IV 3",
input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
expectedOutput: "445ad8c805858dbf70b6af23a151104d96c8f27947f42c5baeae67c6acc35b039fcbfc895fa71c17313df034f01551cb",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "00000000000000000000000000000000"},
{"option": "Hex", "string": "a6eb561ad2f41727"},
"Big", "Hex", "Hex"
]
}
]
},
{
name: "Rabbit Stream Cipher: generated stream should be XORed with the input",
input: "cedda96c054e3ddd93da7ed05e2a4b7bdb0c00fe214f03502e2708b2c2bfc77aa2311b0b9af8aa78d119f92b26db0a6b",
expectedOutput: "7f8afd9c33ebeb3166b13bf64260bc7953e4d8ebe4d30f69554e64f54b794ddd5627bac8eaf47e290b7128a330a8dcfd",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "00000000000000000000000000000000"},
{"option": "Hex", "string": ""},
"Big", "Hex", "Hex"
]
}
]
},
{
name: "Rabbit Stream Cipher: least significant bits should be used for the last block",
input: "0000000000000000",
expectedOutput: "f56b45261c4af702",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "00000000000000000000000000000000"},
{"option": "Hex", "string": ""},
"Big", "Hex", "Hex"
]
}
]
},
{
name: "Rabbit Stream Cipher: invalid key length",
input: "",
expectedOutput: "Invalid key length: 8 bytes (expected: 16)",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "0000000000000000"},
{"option": "Hex", "string": ""},
"Big", "Hex", "Hex"
]
}
]
},
{
name: "Rabbit Stream Cipher: invalid IV length",
input: "",
expectedOutput: "Invalid IV length: 4 bytes (expected: 0 or 8)",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "00000000000000000000000000000000"},
{"option": "Hex", "string": "00000000"},
"Big", "Hex", "Hex"
]
}
]
},
{
// this testcase is taken from the first example on Crypto++ Wiki
// https://www.cryptopp.com/wiki/Rabbit
name: "Rabbit Stream Cipher: little-endian mode (Crypto++ compatible)",
input: "Rabbit stream cipher test",
expectedOutput: "1ae2d4edcf9b6063b00fd6fda0b223aded157e77031cf0440b",
recipeConfig: [
{
"op": "Rabbit Stream Cipher",
"args": [
{"option": "Hex", "string": "23c2731e8b5469fd8dabb5bc592a0f3a"},
{"option": "Hex", "string": "712906405ef03201"},
"Little", "Raw", "Hex"
]
}
]
},
]);