Python MurmurHash2 Code Example (Online Runner)

Python MurmurHash2 examples with variant selection, seeds, and number/hex output matching the MurmurHash2 tools.

Online calculator: use the site MurmurHash2 text tool.

Calculation method

MurmurHash2 supports multiple variants (32-bit x86, 64-bit x64/x86, and the Merkle-Damgard flavor). The tool lets you pick the variant and output format. This helper mirrors those options and uses the same 32-bit seed.

Implementation notes

  • Package: no external dependency; the implementation is pure Python.
  • Implementation: variants are selected explicitly and the seed is parsed as 32-bit. Output is formatted as decimal or hex.
  • Notes: MurmurHash2 is not cryptographic. Different variants are not interchangeable; be consistent when matching other implementations.
python
from pathlib import Path
from typing import Literal
import tempfile

Variant = Literal[
    "x86_32",
    "x86_32a",
    "neutral_32",
    "aligned_32",
    "x64_64a",
    "x86_64b",
]
OutputFormat = Literal["number", "hex"]

UINT32_MASK = 0xFFFFFFFF
UINT64_MASK = (1 << 64) - 1


def parse_seed(value: str) -> int:
    if not value.strip():
        return 0
    cleaned = value.strip()
    base = 16 if cleaned.startswith(("0x", "0X")) else 10
    return int(cleaned, base) & UINT32_MASK


def _mul32(a: int, b: int) -> int:
    return (a * b) & UINT32_MASK


def _read_u32_le(data: bytes, offset: int) -> int:
    return int.from_bytes(data[offset : offset + 4], "little")


def _read_u64_le(data: bytes, offset: int) -> int:
    return int.from_bytes(data[offset : offset + 8], "little") & UINT64_MASK


def _murmur2_x86_32(data: bytes, seed: int) -> int:
    m = 0x5BD1E995
    r = 24
    length = len(data)
    h = (seed ^ length) & UINT32_MASK
    index = 0

    while length >= 4:
        k = _read_u32_le(data, index)
        k = _mul32(k, m)
        k ^= (k >> r) & UINT32_MASK
        k = _mul32(k, m)
        h = _mul32(h, m)
        h ^= k
        index += 4
        length -= 4

    if length == 3:
        h ^= data[index + 2] << 16
    if length >= 2:
        h ^= data[index + 1] << 8
    if length >= 1:
        h ^= data[index]
        h = _mul32(h, m)

    h ^= (h >> 13) & UINT32_MASK
    h = _mul32(h, m)
    h ^= (h >> 15) & UINT32_MASK
    return h & UINT32_MASK


def _mmix(h: int, k: int) -> int:
    m = 0x5BD1E995
    r = 24
    k = _mul32(k, m)
    k ^= (k >> r) & UINT32_MASK
    k = _mul32(k, m)
    h = _mul32(h, m)
    h ^= k
    return h & UINT32_MASK


def _murmur2a(data: bytes, seed: int) -> int:
    m = 0x5BD1E995
    r = 24
    length = len(data)
    h = seed & UINT32_MASK
    index = 0
    remaining = length

    while remaining >= 4:
        k = _read_u32_le(data, index)
        h = _mmix(h, k)
        index += 4
        remaining -= 4

    t = 0
    if remaining == 3:
        t ^= data[index + 2] << 16
    if remaining >= 2:
        t ^= data[index + 1] << 8
    if remaining >= 1:
        t ^= data[index]

    h = _mmix(h, t)
    h = _mmix(h, length)

    h ^= (h >> 13) & UINT32_MASK
    h = _mul32(h, m)
    h ^= (h >> 15) & UINT32_MASK
    return h & UINT32_MASK


def _murmur64a(data: bytes, seed: int) -> int:
    m = 0xC6A4A7935BD1E995
    r = 47
    h = (seed ^ (len(data) * m)) & UINT64_MASK
    index = 0

    while index + 8 <= len(data):
        k = _read_u64_le(data, index)
        k = (k * m) & UINT64_MASK
        k ^= (k >> r)
        k = (k * m) & UINT64_MASK
        h ^= k
        h = (h * m) & UINT64_MASK
        index += 8

    remaining = len(data) - index
    if remaining >= 7:
        h ^= data[index + 6] << 48
    if remaining >= 6:
        h ^= data[index + 5] << 40
    if remaining >= 5:
        h ^= data[index + 4] << 32
    if remaining >= 4:
        h ^= data[index + 3] << 24
    if remaining >= 3:
        h ^= data[index + 2] << 16
    if remaining >= 2:
        h ^= data[index + 1] << 8
    if remaining >= 1:
        h ^= data[index]
        h = (h * m) & UINT64_MASK

    h ^= h >> r
    h = (h * m) & UINT64_MASK
    h ^= h >> r
    return h & UINT64_MASK


def _murmur64b(data: bytes, seed: int) -> int:
    m = 0x5BD1E995
    r = 24
    length = len(data)
    index = 0

    seed_low = seed & UINT32_MASK
    seed_high = (seed >> 32) & UINT32_MASK

    h1 = (seed_low ^ length) & UINT32_MASK
    h2 = seed_high & UINT32_MASK

    while length >= 8:
        k1 = _read_u32_le(data, index)
        index += 4
        length -= 4
        k1 = _mul32(k1, m)
        k1 ^= (k1 >> r) & UINT32_MASK
        k1 = _mul32(k1, m)
        h1 = _mul32(h1, m)
        h1 ^= k1

        k2 = _read_u32_le(data, index)
        index += 4
        length -= 4
        k2 = _mul32(k2, m)
        k2 ^= (k2 >> r) & UINT32_MASK
        k2 = _mul32(k2, m)
        h2 = _mul32(h2, m)
        h2 ^= k2

    if length >= 4:
        k1 = _read_u32_le(data, index)
        index += 4
        length -= 4
        k1 = _mul32(k1, m)
        k1 ^= (k1 >> r) & UINT32_MASK
        k1 = _mul32(k1, m)
        h1 = _mul32(h1, m)
        h1 ^= k1

    if length > 0:
        if length == 3:
            h2 ^= data[index + 2] << 16
        if length >= 2:
            h2 ^= data[index + 1] << 8
        if length >= 1:
            h2 ^= data[index]
            h2 = _mul32(h2, m)

    h1 ^= (h2 >> 18) & UINT32_MASK
    h1 = _mul32(h1, m)
    h2 ^= (h1 >> 22) & UINT32_MASK
    h2 = _mul32(h2, m)
    h1 ^= (h2 >> 17) & UINT32_MASK
    h1 = _mul32(h1, m)
    h2 ^= (h1 >> 19) & UINT32_MASK
    h2 = _mul32(h2, m)

    return ((h1 << 32) | h2) & UINT64_MASK


def murmur2_hash(text: str, variant: Variant = "x86_32", seed: int = 0, output_format: OutputFormat = "number") -> str:
    data = text.encode("utf-8")
    if variant == "x86_32":
        value = _murmur2_x86_32(data, seed)
        return str(value) if output_format == "number" else f"{value:08x}"
    if variant == "x86_32a":
        value = _murmur2a(data, seed)
        return str(value) if output_format == "number" else f"{value:08x}"
    if variant in {"neutral_32", "aligned_32"}:
        value = _murmur2_x86_32(data, seed)
        return str(value) if output_format == "number" else f"{value:08x}"
    if variant == "x64_64a":
        value = _murmur64a(data, seed)
        return str(value) if output_format == "number" else f"{value:016x}"
    value = _murmur64b(data, seed)
    return str(value) if output_format == "number" else f"{value:016x}"


def murmur2_file(path: Path, variant: Variant = "x86_32", seed: int = 0, output_format: OutputFormat = "number") -> str:
    return murmur2_hash(path.read_bytes().decode("utf-8", errors="replace"), variant, seed, output_format)

# Example usage
seed = parse_seed("0x2a")

print(murmur2_hash("hello", variant="x86_32", seed=seed, output_format="number"))
print(murmur2_hash("hello", variant="x64_64a", seed=seed, output_format="hex"))

with tempfile.TemporaryDirectory() as temp_dir:
    sample_path = Path(temp_dir) / "sample.txt"
    sample_path.write_text("hello", encoding="utf-8")
    print(murmur2_file(sample_path, variant="x86_32", seed=seed, output_format="hex"))

Complete script (implementation + tests)

python
from pathlib import Path
from typing import Literal

Variant = Literal[
    "x86_32",
    "x86_32a",
    "neutral_32",
    "aligned_32",
    "x64_64a",
    "x86_64b",
]
OutputFormat = Literal["number", "hex"]

UINT32_MASK = 0xFFFFFFFF
UINT64_MASK = (1 << 64) - 1


def parse_seed(value: str) -> int:
    if not value.strip():
        return 0
    cleaned = value.strip()
    base = 16 if cleaned.startswith(("0x", "0X")) else 10
    return int(cleaned, base) & UINT32_MASK


def _mul32(a: int, b: int) -> int:
    return (a * b) & UINT32_MASK


def _read_u32_le(data: bytes, offset: int) -> int:
    return int.from_bytes(data[offset : offset + 4], "little")


def _read_u64_le(data: bytes, offset: int) -> int:
    return int.from_bytes(data[offset : offset + 8], "little") & UINT64_MASK


def _murmur2_x86_32(data: bytes, seed: int) -> int:
    m = 0x5BD1E995
    r = 24
    length = len(data)
    h = (seed ^ length) & UINT32_MASK
    index = 0

    while length >= 4:
        k = _read_u32_le(data, index)
        k = _mul32(k, m)
        k ^= (k >> r) & UINT32_MASK
        k = _mul32(k, m)
        h = _mul32(h, m)
        h ^= k
        index += 4
        length -= 4

    if length == 3:
        h ^= data[index + 2] << 16
    if length >= 2:
        h ^= data[index + 1] << 8
    if length >= 1:
        h ^= data[index]
        h = _mul32(h, m)

    h ^= (h >> 13) & UINT32_MASK
    h = _mul32(h, m)
    h ^= (h >> 15) & UINT32_MASK
    return h & UINT32_MASK


def _mmix(h: int, k: int) -> int:
    m = 0x5BD1E995
    r = 24
    k = _mul32(k, m)
    k ^= (k >> r) & UINT32_MASK
    k = _mul32(k, m)
    h = _mul32(h, m)
    h ^= k
    return h & UINT32_MASK


def _murmur2a(data: bytes, seed: int) -> int:
    m = 0x5BD1E995
    r = 24
    length = len(data)
    h = seed & UINT32_MASK
    index = 0
    remaining = length

    while remaining >= 4:
        k = _read_u32_le(data, index)
        h = _mmix(h, k)
        index += 4
        remaining -= 4

    t = 0
    if remaining == 3:
        t ^= data[index + 2] << 16
    if remaining >= 2:
        t ^= data[index + 1] << 8
    if remaining >= 1:
        t ^= data[index]

    h = _mmix(h, t)
    h = _mmix(h, length)

    h ^= (h >> 13) & UINT32_MASK
    h = _mul32(h, m)
    h ^= (h >> 15) & UINT32_MASK
    return h & UINT32_MASK


def _murmur64a(data: bytes, seed: int) -> int:
    m = 0xC6A4A7935BD1E995
    r = 47
    h = (seed ^ (len(data) * m)) & UINT64_MASK
    index = 0

    while index + 8 <= len(data):
        k = _read_u64_le(data, index)
        k = (k * m) & UINT64_MASK
        k ^= (k >> r)
        k = (k * m) & UINT64_MASK
        h ^= k
        h = (h * m) & UINT64_MASK
        index += 8

    remaining = len(data) - index
    if remaining >= 7:
        h ^= data[index + 6] << 48
    if remaining >= 6:
        h ^= data[index + 5] << 40
    if remaining >= 5:
        h ^= data[index + 4] << 32
    if remaining >= 4:
        h ^= data[index + 3] << 24
    if remaining >= 3:
        h ^= data[index + 2] << 16
    if remaining >= 2:
        h ^= data[index + 1] << 8
    if remaining >= 1:
        h ^= data[index]
        h = (h * m) & UINT64_MASK

    h ^= h >> r
    h = (h * m) & UINT64_MASK
    h ^= h >> r
    return h & UINT64_MASK


def _murmur64b(data: bytes, seed: int) -> int:
    m = 0x5BD1E995
    r = 24
    length = len(data)
    index = 0

    seed_low = seed & UINT32_MASK
    seed_high = (seed >> 32) & UINT32_MASK

    h1 = (seed_low ^ length) & UINT32_MASK
    h2 = seed_high & UINT32_MASK

    while length >= 8:
        k1 = _read_u32_le(data, index)
        index += 4
        length -= 4
        k1 = _mul32(k1, m)
        k1 ^= (k1 >> r) & UINT32_MASK
        k1 = _mul32(k1, m)
        h1 = _mul32(h1, m)
        h1 ^= k1

        k2 = _read_u32_le(data, index)
        index += 4
        length -= 4
        k2 = _mul32(k2, m)
        k2 ^= (k2 >> r) & UINT32_MASK
        k2 = _mul32(k2, m)
        h2 = _mul32(h2, m)
        h2 ^= k2

    if length >= 4:
        k1 = _read_u32_le(data, index)
        index += 4
        length -= 4
        k1 = _mul32(k1, m)
        k1 ^= (k1 >> r) & UINT32_MASK
        k1 = _mul32(k1, m)
        h1 = _mul32(h1, m)
        h1 ^= k1

    if length > 0:
        if length == 3:
            h2 ^= data[index + 2] << 16
        if length >= 2:
            h2 ^= data[index + 1] << 8
        if length >= 1:
            h2 ^= data[index]
            h2 = _mul32(h2, m)

    h1 ^= (h2 >> 18) & UINT32_MASK
    h1 = _mul32(h1, m)
    h2 ^= (h1 >> 22) & UINT32_MASK
    h2 = _mul32(h2, m)
    h1 ^= (h2 >> 17) & UINT32_MASK
    h1 = _mul32(h1, m)
    h2 ^= (h1 >> 19) & UINT32_MASK
    h2 = _mul32(h2, m)

    return ((h1 << 32) | h2) & UINT64_MASK


def murmur2_hash(text: str, variant: Variant = "x86_32", seed: int = 0, output_format: OutputFormat = "number") -> str:
    data = text.encode("utf-8")
    if variant == "x86_32":
        value = _murmur2_x86_32(data, seed)
        return str(value) if output_format == "number" else f"{value:08x}"
    if variant == "x86_32a":
        value = _murmur2a(data, seed)
        return str(value) if output_format == "number" else f"{value:08x}"
    if variant in {"neutral_32", "aligned_32"}:
        value = _murmur2_x86_32(data, seed)
        return str(value) if output_format == "number" else f"{value:08x}"
    if variant == "x64_64a":
        value = _murmur64a(data, seed)
        return str(value) if output_format == "number" else f"{value:016x}"
    value = _murmur64b(data, seed)
    return str(value) if output_format == "number" else f"{value:016x}"


def murmur2_file(path: Path, variant: Variant = "x86_32", seed: int = 0, output_format: OutputFormat = "number") -> str:
    return murmur2_hash(path.read_bytes().decode("utf-8", errors="replace"), variant, seed, output_format)


def run_tests() -> None:
    assert murmur2_hash("", variant="x86_32", seed=0, output_format="hex") == "00000000"
    assert murmur2_hash("hello", variant="x86_32", seed=0, output_format="hex") == "e56129cb"
    assert murmur2_hash("hello", variant="x64_64a", seed=0, output_format="hex") == "1e68d17c457bf117"
    print("MurmurHash2 tests passed")


if __name__ == "__main__":
    run_tests()