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 →