forked from gchq/CyberChef
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request gchq#1694 from AliceGrey/master
Add MurmurHash3 Operation
- Loading branch information
Showing
4 changed files
with
222 additions
and
0 deletions.
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 |
---|---|---|
|
@@ -369,6 +369,7 @@ | |
"SHA2", | ||
"SHA3", | ||
"SM3", | ||
"MurmurHash3", | ||
"Keccak", | ||
"Shake", | ||
"RIPEMD", | ||
|
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,143 @@ | ||
/** | ||
* Based on murmurhash-js (https://github.com/garycourt/murmurhash-js) | ||
* @author Gary Court | ||
* @license MIT | ||
* | ||
* @author AliceGrey [[email protected]] | ||
* @copyright Crown Copyright 2024 | ||
* @license Apache-2.0 | ||
*/ | ||
|
||
import Operation from "../Operation.mjs"; | ||
|
||
/** | ||
* MurmurHash3 operation | ||
*/ | ||
class MurmurHash3 extends Operation { | ||
|
||
/** | ||
* MurmurHash3 constructor | ||
*/ | ||
constructor() { | ||
super(); | ||
|
||
this.name = "MurmurHash3"; | ||
this.module = "Default"; | ||
this.description = "Generates a MurmurHash v3 for a string input and an optional seed input"; | ||
this.infoURL = "https://wikipedia.org/wiki/MurmurHash"; | ||
this.inputType = "string"; | ||
this.outputType = "number"; | ||
this.args = [ | ||
{ | ||
name: "Seed", | ||
type: "number", | ||
value: 0 | ||
}, | ||
{ | ||
name: "Convert to Signed", | ||
type: "boolean", | ||
value: false | ||
} | ||
]; | ||
} | ||
|
||
/** | ||
* Calculates the MurmurHash3 hash of the input. | ||
* Based on Gary Court's JS MurmurHash implementation | ||
* @see http://github.com/garycourt/murmurhash-js | ||
* @author AliceGrey [[email protected]] | ||
* @param {string} input ASCII only | ||
* @param {number} seed Positive integer only | ||
* @return {number} 32-bit positive integer hash | ||
*/ | ||
mmh3(input, seed) { | ||
let h1b; | ||
let k1; | ||
const remainder = input.length & 3; // input.length % 4 | ||
const bytes = input.length - remainder; | ||
let h1 = seed; | ||
const c1 = 0xcc9e2d51; | ||
const c2 = 0x1b873593; | ||
let i = 0; | ||
|
||
while (i < bytes) { | ||
k1 = | ||
((input.charCodeAt(i) & 0xff)) | | ||
((input.charCodeAt(++i) & 0xff) << 8) | | ||
((input.charCodeAt(++i) & 0xff) << 16) | | ||
((input.charCodeAt(++i) & 0xff) << 24); | ||
++i; | ||
|
||
k1 = ((((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16))) & 0xffffffff; | ||
k1 = (k1 << 15) | (k1 >>> 17); | ||
k1 = ((((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16))) & 0xffffffff; | ||
|
||
h1 ^= k1; | ||
h1 = (h1 << 13) | (h1 >>> 19); | ||
h1b = ((((h1 & 0xffff) * 5) + ((((h1 >>> 16) * 5) & 0xffff) << 16))) & 0xffffffff; | ||
h1 = (((h1b & 0xffff) + 0x6b64) + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16)); | ||
} | ||
|
||
k1 = 0; | ||
|
||
if (remainder === 3) { | ||
k1 ^= (input.charCodeAt(i + 2) & 0xff) << 16; | ||
} | ||
|
||
if (remainder === 3 || remainder === 2) { | ||
k1 ^= (input.charCodeAt(i + 1) & 0xff) << 8; | ||
} | ||
|
||
if (remainder === 3 || remainder === 2 || remainder === 1) { | ||
k1 ^= (input.charCodeAt(i) & 0xff); | ||
|
||
k1 = (((k1 & 0xffff) * c1) + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff; | ||
k1 = (k1 << 15) | (k1 >>> 17); | ||
k1 = (((k1 & 0xffff) * c2) + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff; | ||
h1 ^= k1; | ||
} | ||
|
||
h1 ^= input.length; | ||
|
||
h1 ^= h1 >>> 16; | ||
h1 = (((h1 & 0xffff) * 0x85ebca6b) + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff; | ||
h1 ^= h1 >>> 13; | ||
h1 = ((((h1 & 0xffff) * 0xc2b2ae35) + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16))) & 0xffffffff; | ||
h1 ^= h1 >>> 16; | ||
|
||
return h1 >>> 0; | ||
} | ||
|
||
/** | ||
* Converts an unsigned 32-bit integer to a signed 32-bit integer | ||
* @author AliceGrey [[email protected]] | ||
* @param {value} 32-bit unsigned integer | ||
* @return {number} 32-bit signed integer | ||
*/ | ||
unsignedToSigned(value) { | ||
if (value & 0x80000000) { | ||
return -0x100000000 + value; | ||
} else { | ||
return value; | ||
} | ||
} | ||
|
||
/** | ||
* @param {string} input | ||
* @param {Object[]} args | ||
* @returns {number} | ||
*/ | ||
run(input, args) { | ||
if (args && args.length >= 1) { | ||
const seed = args[0]; | ||
const hash = this.mmh3(input, seed); | ||
if (args.length > 1 && args[1]) { | ||
return this.unsignedToSigned(hash); | ||
} | ||
return hash; | ||
} | ||
return this.mmh3(input); | ||
} | ||
} | ||
|
||
export default MurmurHash3; |
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
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,77 @@ | ||
/** | ||
* MurmurHash3 tests | ||
* @author AliceGrey [[email protected]] | ||
* @copyright Crown Copyright 2024 | ||
* @license Apache-2.0 | ||
*/ | ||
|
||
import TestRegister from "../../lib/TestRegister.mjs"; | ||
|
||
TestRegister.addTests([ | ||
{ | ||
name: "To MurmurHash3: nothing", | ||
input: "", | ||
expectedOutput: "0", | ||
recipeConfig: [ | ||
{ | ||
op: "MurmurHash3", | ||
args: [0], | ||
}, | ||
], | ||
}, | ||
{ | ||
name: "To MurmurHash3: 1", | ||
input: "1", | ||
expectedOutput: "2484513939", | ||
recipeConfig: [ | ||
{ | ||
op: "MurmurHash3", | ||
args: [0], | ||
}, | ||
], | ||
}, | ||
{ | ||
name: "To MurmurHash3: Hello World!", | ||
input: "Hello World!", | ||
expectedOutput: "3691591037", | ||
recipeConfig: [ | ||
{ | ||
op: "MurmurHash3", | ||
args: [0], | ||
}, | ||
], | ||
}, | ||
{ | ||
name: "To MurmurHash3: Hello World! with seed", | ||
input: "Hello World!", | ||
expectedOutput: "1148600031", | ||
recipeConfig: [ | ||
{ | ||
op: "MurmurHash3", | ||
args: [1337], | ||
}, | ||
], | ||
}, | ||
{ | ||
name: "To MurmurHash3: foo", | ||
input: "foo", | ||
expectedOutput: "4138058784", | ||
recipeConfig: [ | ||
{ | ||
op: "MurmurHash3", | ||
args: [0], | ||
}, | ||
], | ||
}, | ||
{ | ||
name: "To MurmurHash3: foo signed", | ||
input: "foo", | ||
expectedOutput: "-156908512", | ||
recipeConfig: [ | ||
{ | ||
op: "MurmurHash3", | ||
args: [0, true], | ||
}, | ||
], | ||
} | ||
]); |