Skip to content

Secure Token Generator — PHP source

Generate cryptographically-secure random tokens in your browser. Pick the entropy size and format - hex, base32, base64, base62, or alphanumeric - and see the real strength in bits. Runs entirely client-side.

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

<?php
// ─────────────────────────────────────────────────────────────────────────────
// Secure token generator — PHP polyglot showcase port.
// Language: PHP 8.1+ (standard library only, no Composer dependencies).
//
// CosmoDev polyglot showcase port of `token-generator`, ported from the
// canonical TypeScript logic in src/lib/token-generator.ts.
//
// This is display source — part of CosmoDev's polyglot tool pages, where each
// tool's pure logic is shown side-by-side in many languages.
//
// Design:
//   - Pure logic with an *injectable* RNG, so generation is unit-testable
//     without touching the secure RNG. Callers pass null for the CSPRNG
//     default; tests pass a seeded generator for exact, reproducible output.
//   - Each output symbol is selected without modulo bias via rejection
//     sampling (see `constantTimeSelect`), so even non-power-of-two
//     alphabets like base62 are unbiased.
//
// CSPRNG: PHP's `random_int()` reads from the kernel's cryptographically
// secure RNG (getrandom on Linux, BCryptGenRandom on Windows) and is itself
// unbiased over its range.
// ─────────────────────────────────────────────────────────────────────────────

declare(strict_types=1);

/**
 * The fixed alphabet strings. 'custom' is intentionally absent here — its
 * symbols are caller-supplied via $opts['customAlphabet'].
 */
const ALPHABETS = [
    'hex'                => '0123456789abcdef',
    'hex-upper'          => '0123456789ABCDEF',
    'base32'             => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',           // RFC 4648
    'base32-crockford'   => '0123456789ABCDEFGHJKMNPQRSTVWXYZ',         // Crockford (no I/L/O/U)
    'base64'             => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
    'base64url'          => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
    'base62'             => '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
    'alphanumeric'       => '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
];

/** Convenience encoding aliases that map 1:1 onto a fixed alphabet. */
const ENCODING_TO_ALPHABET = [
    'hex'         => 'hex',
    'base32'      => 'base32',
    'base64'      => 'base64',
    'base64url'   => 'base64url',
    'base62'      => 'base62',
    'alphanumeric'=> 'alphanumeric',
];

/**
 * Default CSPRNG-backed [0, 1) float, used when no `rng` is injected.
 *
 * `random_int(0, 0xFFFFFFFF)` draws a cryptographically secure, unbiased
 * 32-bit unsigned integer (PHP 7+ on 64-bit builds). Dividing by 2^32 maps
 * all 4 294 967 296 outcomes uniformly into the half-open unit interval.
 */
function defaultRng(): float {
    return random_int(0, 0xFFFFFFFF) / 4294967296.0;
}

/**
 * Resolve the effective alphabet string from options and an optional encoding.
 *
 * `$opts` is a plain array shaped like:
 *   ['alphabet' => string|null, 'customAlphabet' => string|null, 'rng' => callable|null]
 *
 * Precedence: explicit `alphabet` → `$encoding` → 'hex'. A 'custom' alphabet
 * with no/empty `customAlphabet` resolves to '' (an invalid, empty set).
 */
function resolveAlphabet(?array $opts = null, ?string $encoding = null): string {
    if (($opts['alphabet'] ?? null) === 'custom') {
        return $opts['customAlphabet'] ?? '';
    }
    if (!empty($opts['alphabet']) && isset(ALPHABETS[$opts['alphabet']])) {
        return ALPHABETS[$opts['alphabet']];
    }
    if ($encoding !== null
        && isset(ENCODING_TO_ALPHABET[$encoding])
        && isset(ALPHABETS[ENCODING_TO_ALPHABET[$encoding]])) {
        return ALPHABETS[ENCODING_TO_ALPHABET[$encoding]];
    }
    return ALPHABETS['hex'];
}

/**
 * Build an `$n`-character string from `$alphabet`, selecting each symbol
 * WITHOUT modulo bias via rejection sampling.
 *
 * Naive `draw % size` is biased whenever `size` does not divide the draw
 * range: for base62 the trailing symbols would be slightly over-represented.
 * Instead we reject any 32-bit draw that lands in the uneven remainder
 * (>= $limit) and redraw, so every symbol is exactly equally likely. The
 * `$guard` cap keeps a pathological/constant RNG from looping forever.
 */
function constantTimeSelect(string $alphabet, int $n, ?callable $rng = null): string {
    $r = $rng ?? 'defaultRng';
    $size = strlen($alphabet); // alphabets are ASCII, so bytes == chars
    if ($size < 1 || $n < 1) {
        return '';
    }
    // Largest multiple of $size that fits in [0, 2^32-1]. Draws at or above
    // this boundary map unevenly under `% $size`, so we redraw.
    $limit = floor(0xFFFFFFFF / $size) * $size;
    $out = '';
    for ($i = 0; $i < $n; $i++) {
        $x = $r() * 4294967296.0; // [0, 2^32)
        $guard = 0;
        while ($x >= $limit && $guard < 64) {
            $x = $r() * 4294967296.0;
            $guard++;
        }
        $out .= $alphabet[(int) floor($x) % $size];
    }
    return $out;
}

/**
 * Output characters needed to carry `$bytes` bytes of entropy through an
 * alphabet of `$alphabetSize` symbols.
 */
function outputLength(float $bytes, int $alphabetSize): int {
    if (!is_finite($bytes) || $bytes < 1.0 || $alphabetSize < 2) {
        return 0;
    }
    return (int) ceil(($bytes * 8.0) / log($alphabetSize, 2));
}

/**
 * Generate a token carrying `$bytes` bytes of underlying entropy, rendered
 * through `$opts['alphabet']` (or `$encoding`). Each character is sampled
 * uniformly without modulo bias, so output is unbiased even for base62.
 * Returns '' for invalid input (non-positive/non-finite bytes, an alphabet
 * under 2 symbols).
 *
 * Example: generateToken(16, ['alphabet' => 'hex']) → 32 hex chars (128 bits).
 */
function generateToken(float $bytes, ?array $opts = null, ?string $encoding = null): string {
    if (!is_finite($bytes) || $bytes < 1.0) {
        return '';
    }
    $alphabet = resolveAlphabet($opts, $encoding);
    if (strlen($alphabet) < 2) {
        return '';
    }
    $n = outputLength($bytes, strlen($alphabet));
    $rng = $opts['rng'] ?? null;
    return constantTimeSelect($alphabet, $n, $rng);
}

/**
 * Entropy (in bits) of a token of `$bytes` entropy in a `size`-symbol
 * alphabet. Equals outputLength * log2(size), which is >= bytes*8 because the
 * character count is rounded up.
 */
function estimateEntropy(float $bytes, int $alphabetSize): float {
    if (!is_finite($bytes) || $bytes < 1.0 || $alphabetSize < 2) {
        return 0.0;
    }
    return (float) outputLength($bytes, $alphabetSize) * log($alphabetSize, 2);
}

/**
 * Bucket an entropy estimate (bits) into a human strength label.
 * Tiers: weak <64 · fair 64–127 · strong 128–255 · very strong ≥256.
 */
function strengthLabel(float $entropyBits): string {
    if (!is_finite($entropyBits) || $entropyBits < 64.0) {
        return 'weak';
    }
    if ($entropyBits < 128.0) {
        return 'fair';
    }
    if ($entropyBits < 256.0) {
        return 'strong';
    }
    return 'very strong';
}

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 →