mirror of
https://github.com/gchq/CyberChef.git
synced 2026-01-30 04:10:31 -08:00
feat: add random integer generation operation
This commit is contained in:
parent
2a1294f1c0
commit
9f7d1bf0ad
2 changed files with 146 additions and 0 deletions
|
|
@ -549,6 +549,7 @@
|
|||
"P-list Viewer",
|
||||
"Disassemble x86",
|
||||
"Pseudo-Random Number Generator",
|
||||
"Pseudo-Random Integer Generator",
|
||||
"Generate De Bruijn Sequence",
|
||||
"Generate UUID",
|
||||
"Analyse UUID",
|
||||
|
|
|
|||
145
src/core/operations/PseudoRandomIntegerGenerator.mjs
Normal file
145
src/core/operations/PseudoRandomIntegerGenerator.mjs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
* @author cktgh [chankaitung@gmail.com]
|
||||
* @copyright Crown Copyright 2026
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import OperationError from "../errors/OperationError.mjs";
|
||||
import forge from "node-forge";
|
||||
import Utils, { isWorkerEnvironment } from "../Utils.mjs";
|
||||
import { DELIM_OPTIONS } from "../lib/Delim.mjs";
|
||||
|
||||
/**
|
||||
* Pseudo-Random Integer Generator operation
|
||||
*/
|
||||
class PseudoRandomIntegerGenerator extends Operation {
|
||||
|
||||
// in theory 2**53 is the max range, but we use Number.MAX_SAFE_INTEGER (2**53 - 1) as it is more consistent.
|
||||
static MAX_RANGE = Number.MAX_SAFE_INTEGER;
|
||||
// arbitrary choice
|
||||
static BUFFER_SIZE = 1024;
|
||||
|
||||
/**
|
||||
* PseudoRandomIntegerGenerator constructor
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.name = "Pseudo-Random Integer Generator";
|
||||
this.module = "Ciphers";
|
||||
this.description = "A cryptographically-secure pseudo-random number generator (PRNG).<br><br>Generates random integers within a specified range using the browser's built-in <code>crypto.getRandomValues()</code> method if available.<br><br>The supported range of integers is from <code>-(2^53 - 1)</code> to <code>(2^53 - 1)</code>.";
|
||||
this.infoURL = "https://wikipedia.org/wiki/Pseudorandom_number_generator";
|
||||
this.inputType = "string";
|
||||
this.outputType = "string";
|
||||
this.args = [
|
||||
{
|
||||
"name": "Number of Integers",
|
||||
"type": "number",
|
||||
"value": 1,
|
||||
"min": 1
|
||||
},
|
||||
{
|
||||
"name": "Min Value",
|
||||
"type": "number",
|
||||
"value": 0,
|
||||
"min": Number.MIN_SAFE_INTEGER,
|
||||
"max": Number.MAX_SAFE_INTEGER
|
||||
},
|
||||
{
|
||||
"name": "Max Value",
|
||||
"type": "number",
|
||||
"value": 99,
|
||||
"min": Number.MIN_SAFE_INTEGER,
|
||||
"max": Number.MAX_SAFE_INTEGER
|
||||
},
|
||||
{
|
||||
"name": "Delimiter",
|
||||
"type": "option",
|
||||
"value": DELIM_OPTIONS
|
||||
}
|
||||
];
|
||||
|
||||
// not using BigUint64Array to avoid BigInt handling overhead
|
||||
this.randomBuffer = new Uint32Array(PseudoRandomIntegerGenerator.BUFFER_SIZE);
|
||||
this.randomBufferOffset = PseudoRandomIntegerGenerator.BUFFER_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {Object[]} args
|
||||
* @returns {string}
|
||||
*/
|
||||
run(input, args) {
|
||||
const [numInts, minInt, maxInt, delimiter] = args;
|
||||
|
||||
if (minInt === null || maxInt === null) return "";
|
||||
|
||||
const min = Math.ceil(minInt);
|
||||
const max = Math.floor(maxInt);
|
||||
const delim = Utils.charRep(delimiter || "Space");
|
||||
|
||||
if (!Number.isSafeInteger(min) || !Number.isSafeInteger(max)) {
|
||||
throw new OperationError("Min and Max must be between `-(2^53 - 1)` and `2^53 - 1`.");
|
||||
}
|
||||
if (min > max) {
|
||||
throw new OperationError("Min cannot be larger than Max.");
|
||||
}
|
||||
const range = max - min + 1; // inclusive range
|
||||
if (range > PseudoRandomIntegerGenerator.MAX_RANGE) {
|
||||
throw new OperationError("Range between Min and Max cannot be larger than `2^53`");
|
||||
}
|
||||
|
||||
// as large as possible while divisible by range
|
||||
const rejectionThreshold = PseudoRandomIntegerGenerator.MAX_RANGE - (PseudoRandomIntegerGenerator.MAX_RANGE % range);
|
||||
const output = [];
|
||||
for (let i = 0; i < numInts; i++) {
|
||||
const result = this._generateRandomValue(rejectionThreshold);
|
||||
const intValue = min + (result % range);
|
||||
output.push(intValue.toString());
|
||||
}
|
||||
|
||||
return output.join(delim);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random value, result will be less than the rejection threshold (exclusive).
|
||||
*
|
||||
* @param {number} rejectionThreshold
|
||||
* @returns {number}
|
||||
*/
|
||||
_generateRandomValue(rejectionThreshold) {
|
||||
let result;
|
||||
do {
|
||||
if (this.randomBufferOffset + 2 > this.randomBuffer.length) {
|
||||
this._resetRandomBuffer();
|
||||
}
|
||||
// stitching a 53 bit number; not using BigUint64Array to avoid BigInt handling overhead
|
||||
result = (this.randomBuffer[this.randomBufferOffset++] & 0x1f_ffff) * 0x1_0000_0000 +
|
||||
this.randomBuffer[this.randomBufferOffset++];
|
||||
} while (result >= rejectionThreshold);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill random buffer with new random values and rseet the offset.
|
||||
*/
|
||||
_resetRandomBuffer() {
|
||||
if (isWorkerEnvironment() && self.crypto) {
|
||||
self.crypto.getRandomValues(this.randomBuffer);
|
||||
} else {
|
||||
const bytes = forge.random.getBytesSync(this.randomBuffer.length * 4);
|
||||
for (let j = 0; j < this.randomBuffer.length; j++) {
|
||||
this.randomBuffer[j] = (bytes.charCodeAt(j * 4) << 24) |
|
||||
(bytes.charCodeAt(j * 4 + 1) << 16) |
|
||||
(bytes.charCodeAt(j * 4 + 2) << 8) |
|
||||
bytes.charCodeAt(j * 4 + 3);
|
||||
}
|
||||
}
|
||||
this.randomBufferOffset = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default PseudoRandomIntegerGenerator;
|
||||
Loading…
Add table
Add a link
Reference in a new issue