-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'rabbit-stream-cipher' of https://github.com/mikecat/Cyb…
- Loading branch information
Showing
4 changed files
with
427 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -134,7 +134,8 @@ | |
"Typex", | ||
"Lorenz", | ||
"Colossus", | ||
"SIGABA" | ||
"SIGABA", | ||
"Rabbit Stream Cipher" | ||
] | ||
}, | ||
{ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.