Skip to content

MAC Address Generator — Python source

Generate random EUI-48 MAC addresses with a chosen separator, optional OUI prefix, uppercase formatting, and a locally-administered flag. Runs entirely in your browser with crypto-grade randomness.

This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.

"""mac-address-generator — random EUI-48 MAC address generator.

Language: Python (3.9+, standard library only)
Source:   CosmoDev polyglot showcase port of the MAC Address Generator tool,
          ported from src/lib/mac-generator.ts (the canonical TypeScript
          implementation), kept in lock-step with the Go twin at
          cli/mac-address-generator/mac-address-generator.go.
License:  display source — part of CosmoDev's polyglot tool pages.

Design goals:
  - Pure + deterministic; raises only on a malformed OUI (never on randomness).
  - Functionally equivalent to the TS reference: same inputs -> same outputs.
  - Self-contained: stdlib only (no pip packages). The default RNG uses
    ``os.urandom`` — Python's cryptographic random source.

API note: this port mirrors the TS canonical lib's public API, the deterministic,
RNG-injectable superset of the Go twin's. The Go twin draws from crypto/rand
directly (non-injectable), so its own suite asserts only structural properties
(regex shape, OUI prefix, U/L bit). Like the TS lib, this port accepts an
injected ``rng`` returning a float in [0, 1), letting the showcase tests below
assert exact MAC strings.

Pipeline: (optional OUI -> 3 octets) -> 3 random octets -> (force U/L bit) ->
join 6 octets as %02x with the separator -> (uppercase). A malformed OUI (not
6 hex digits after stripping non-hex chars) raises ValueError.
"""

from __future__ import annotations

import os
import re
from dataclasses import dataclass
from typing import Callable, Optional

__all__ = ["generate_mac", "MacOptions"]


def _default_rng() -> float:
    """A 32-bit draw from ``os.urandom`` mapped to [0, 1) — mirrors the TS
    default ``crypto.getRandomValues(new Uint32Array(1))[0] / 2**32``."""
    return int.from_bytes(os.urandom(4), "big") / 2 ** 32


def _hex(n: int) -> str:
    """Two-digit lowercase hex, mirroring TS ``n.toString(16).padStart(2, '0')``."""
    return format(n, "02x")


@dataclass
class MacOptions:
    """Options mirror the TS ``MacOptions`` interface.

    Every field defaults to the TS default, so callers construct incrementally
    with keyword args.
    """

    separator: Optional[str] = None
    """Joining chars. ``None`` -> default ':'. Empty string concatenates."""

    uppercase: bool = False
    """Uppercase the hex letters in the result."""

    oui: Optional[str] = None
    """Optional 6-hex-digit OUI prefix (separators tolerated, stripped first)."""

    locally_administered: bool = False
    """Force the U/L bit (0x02) of the first octet."""

    rng: Callable[[], float] = _default_rng
    """Random source returning a float in [0, 1). Defaults to a secure draw."""


_NON_HEX_RE = re.compile(r"[^0-9a-fA-F]")


def generate_mac(opts: Optional[MacOptions] = None) -> str:
    """Build a random EUI-48 MAC address.

    The Python twin of ``generateMac()`` in src/lib/mac-generator.ts. Raises
    ``ValueError`` on a malformed OUI; never raises otherwise.
    """
    if opts is None:
        opts = MacOptions()

    sep = opts.separator if opts.separator is not None else ":"

    octets: list[int] = []

    # Octets 0..2 — OUI (validated) or random. TS `if (opts.oui)` truthiness:
    # None and "" both take the random path.
    if opts.oui:
        clean = _NON_HEX_RE.sub("", opts.oui)
        if len(clean) != 6:
            raise ValueError(f'OUI must be 6 hex digits, got "{opts.oui}"')
        octets.append(int(clean[0:2], 16))
        octets.append(int(clean[2:4], 16))
        octets.append(int(clean[4:6], 16))
    else:
        octets.append(int(opts.rng() * 256))
        octets.append(int(opts.rng() * 256))
        octets.append(int(opts.rng() * 256))

    # Octets 3..5 — always random.
    octets.append(int(opts.rng() * 256))
    octets.append(int(opts.rng() * 256))
    octets.append(int(opts.rng() * 256))

    if opts.locally_administered:
        octets[0] = (octets[0] & 0b11111101) | 0b00000010  # set U/L bit, clear multicast

    mac = sep.join(_hex(o) for o in octets)
    if opts.uppercase:
        mac = mac.upper()
    return mac


# ---------- showcase tests (mirror src/lib/mac-generator.test.ts) ----------
# Run: python python.py
if __name__ == "__main__":
    assert generate_mac(MacOptions(rng=lambda: 0.0)) == "00:00:00:00:00:00"

    # separators: dash and concatenate
    assert generate_mac(MacOptions(rng=lambda: 0.0, separator="-")) == "00-00-00-00-00-00"
    assert generate_mac(MacOptions(rng=lambda: 0.0, separator="")) == "000000000000"

    # floor(0.7 * 256) == 179 == 0xb3
    assert generate_mac(MacOptions(rng=lambda: 0.7, uppercase=True)) == "B3:B3:B3:B3:B3:B3"

    # OUI prefix (embedded separators stripped)
    assert generate_mac(MacOptions(rng=lambda: 0.0, oui="aa-bb-cc")) == "aa:bb:cc:00:00:00"

    # locally administered U/L bit: floor(0.5 * 256) == 0x80 -> 0x82
    assert generate_mac(MacOptions(rng=lambda: 0.5, locally_administered=True)) == "82:80:80:80:80:80"

    # malformed OUI -> ValueError
    try:
        generate_mac(MacOptions(rng=lambda: 0.0, oui="abc"))
        raise AssertionError("expected ValueError for short OUI")
    except ValueError:
        pass

    print("ok")

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →