Skip to content

Generar un token aleatorio seguro snippet

Un token es tan fuerte como su fuente de entropía: Math.random, rand(), mt_rand y las semillas por defecto son predecibles, y los tokens predecibles son tokens falsificados.

Un token es tan fuerte como su fuente de entropía: Math.random, rand(), mt_rand y las semillas por defecto son predecibles, y los tokens predecibles son tokens falsificados. La regla en todas partes: aleatoriedad criptográfica del SO (crypto.getRandomValues, crypto/rand, secrets, SecureRandom) codificada como hex o base64url, con el tamaño adecuado al uso — mínimo 16 bytes aleatorios para claves de API, 32 para cualquier cosa que cargue una sesión. La segunda regla: nunca recortes el alfabeto para embellecer los tokens; eso borra bits.

Receta ejecutable · 12 lenguajesAbrir la herramienta token-generator →
Security Hardeningsecurityrandomtokencsprngentropyapi-key

Every language

12 lenguajes, copy-ready. One at a time with syntax highlighting, or all inline.

JSJavaScript
function randomHex(n) {
  const bytes = crypto.getRandomValues(new Uint8Array(n)); // OS CSPRNG
  return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
}

// base64url — the URL-safe alphabet, padding stripped:
function randomBase64Url(n) {
  const bytes = crypto.getRandomValues(new Uint8Array(n));
  let bin = '';
  for (const b of bytes) bin += String.fromCharCode(b);
  return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

randomHex(32); // 64 hex chars — 256 bits of entropy

crypto.randomUUID() is a UUID, not a general token — 122 random bits locked in a fixed 8-4-4-4-12 shape with version/variant bits burned. Math.random() is a seeded PRNG; a token from it is guessable. crypto is global in browsers and Node 19+; older Node needs require('node:crypto').webcrypto.

TSTypeScript
function randomHex(n: number): string {
  const bytes = new Uint8Array(n);
  crypto.getRandomValues(bytes);
  return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
}

function randomBase64Url(n: number): string {
  const bytes = new Uint8Array(n);
  crypto.getRandomValues(bytes);
  let bin = '';
  for (const b of bytes) bin += String.fromCharCode(b);
  return btoa(bin).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '');
}

The btoa-vs-buffer difference: browsers and Node 16+ have global btoa (which chokes on code points > 0xFF — hence the byte-by-byte string build), while Node-side code can shortcut with Buffer.from(bytes).toString('base64url') and skip the tr/replace entirely. The crypto global typechecks only with lib.dom or @types/node in scope.

GoGo
import (
	"crypto/rand"
	"encoding/base64"
	"encoding/hex"
)

func randomHex(n int) (string, error) {
	b := make([]byte, n)
	if _, err := rand.Read(b); err != nil { // OS entropy — crypto/rand
		return "", err
	}
	return hex.EncodeToString(b), nil // 2 hex chars per byte
}

func randomBase64URL(n int) (string, error) {
	b := make([]byte, n)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return base64.RawURLEncoding.EncodeToString(b), nil // -_ alphabet, no padding
}

This is crypto/rand — math/rand is NOT for tokens: its stream is reproducible from a seed, and pre-Go-1.20 an unseeded source was deterministic across runs. RawURLEncoding (no trailing =) keeps the token pasteable into URLs and headers.

RsRust
use rand::rngs::OsRng;
use rand::RngCore;

fn random_hex(n: usize) -> String {
    let mut buf = vec![0u8; n];
    OsRng.fill_bytes(&mut buf); // OS entropy, rejection-sampled, unbiased
    buf.iter().map(|b| format!("{b:02x}")).collect()
}

The trap is reaching for rand::thread_rng() — a fast seeded ChaCha, fine for simulations, wrong for secrets. Go straight to OsRng (or rand::random::<[u8; 32]>() which also draws OS bytes). The getrandom crate is the thin OS syscall wrapper — pick it when rand's trait machinery is more dependency than you need.

PHPPHP
$apiKey  = bin2hex(random_bytes(32));                          // 64 hex chars
$sessionId = rtrim(
    strtr(base64_encode(random_bytes(32)), '+/', '-_'),
    '='
);                                                             // base64url

random_bytes() is PHP 7+'s CSPRNG (getrandom(2) or /dev/urandom); bin2hex doubles the length — 32 bytes in, 64 chars out. mt_rand() and uniqid() are predictable (uniqid is a timestamp) and must never appear near a token.

PyPython
import secrets

api_key   = secrets.token_hex(32)       # 64 hex chars — 256 bits
session_id = secrets.token_urlsafe(32)  # ~43 base64url chars — 256 bits

# comparing tokens later? constant-time, not ==:
ok = secrets.compare_digest(given, expected)

The argument is BYTES of entropy, not output length — token_hex(32) is 64 chars carrying 32 random bytes. The secrets module replaced the old os.urandom-and-hex recipes, and random is NOT for this: the Mersenne Twister's state falls to 624 observed outputs.

C#C#
using System.Security.Cryptography;

static string RandomHex(int n)
{
    byte[] bytes = RandomNumberGenerator.GetBytes(n); // .NET 6+: allocates + fills
    return Convert.ToHexString(bytes).ToLowerInvariant();
}

Watch the RandomNumberGenerator.GetBytes(array) API shape: .NET 6+ has GetBytes(int) returning the filled array, while the older overload fills a preallocated byte[]. Convert.ToHexString emits UPPERCASE — lowercase it for consistency with every other language here. System.Random (even seeded) is predictable; never for tokens.

JvJava
import java.security.SecureRandom;
import java.util.HexFormat;

static final SecureRandom RNG = new SecureRandom();

static String randomHex(int n) {
    byte[] bytes = new byte[n];
    RNG.nextBytes(bytes);
    return HexFormat.of().formatHex(bytes); // Java 17+
}

getInstanceStrong() can block on entropy at boot (NativePRNGBlocking on Linux) — plain new SecureRandom() never blocks and is the default choice. Hoist the instance to a static field: SecureRandom is thread-safe and seeding per call is wasted work. HexFormat is Java 17+; older JDKs need a manual %02x loop.

SwSwift
import Foundation

// random(in:) with the default generator uses SystemRandomNumberGenerator,
// which IS the system CSPRNG (arc4random_buf / BCryptGenRandom):
let bytes = (0..<32).map { _ in UInt8.random(in: .min ... .max) }
let token = bytes.map { String(format: "%02x", $0) }.joined()

The default RNG is the system CSPRNG in Swift — unlike most languages, the plain random(in:) is already cryptographically secure; `using: .system` only makes it explicit. SecRandomCopyBytes is the lower-level Security.framework call when you want an explicit error code instead of a crash on exhaustion.

KtKotlin
import java.security.SecureRandom
import java.util.HexFormat

private val rng = SecureRandom()

fun randomHex(n: Int): String {
    val bytes = ByteArray(n)
    rng.nextBytes(bytes)
    return HexFormat.of().formatHex(bytes)
}

java.util.UUID.randomUUID() is fine as a UUID but 122 bits only — 6 of its 128 bits are version/variant markers, and the 8-4-4-4-12 shape wastes width. For API keys, read real bytes as above; same JVM SecureRandom as the Java recipe.

RbRuby
require 'securerandom'

token  = SecureRandom.hex(32)          # 64 hex chars
b64    = SecureRandom.base64(32)       # padded, may contain + and /
safe   = SecureRandom.base64(32).tr('+/', '-_').delete('=')  # URL-safe

URL-safe variants are a tr('+/', '-_') away — stdlib securerandom ships hex/base64/random_bytes but no urlsafe_base64 (that one is Rails' ActiveSupport). SecureRandom rides OpenSSL's CSPRNG; Random.new.rand is the Mersenne Twister, not a security source.

ZigZig
const std = @import("std");

var buf: [32]u8 = undefined;
std.crypto.random.bytes(&buf); // always OS-backed: getrandom(2), arc4random, RtlGenRandom
const token = std.fmt.bytesToHex(buf, .lower); // [64]u8
std.debug.print("{s}\n", .{token});

std.crypto.random is always OS-backed — it never falls back to a seeded PRNG, so there is no wrong generator to accidentally pick. std.Random.DefaultCsprng exists for streaming a seeded CSPRNG, which is the opposite of what a token wants. bytesToHex returns a comptime-length [2*N]u8 — no allocator needed.

Keep going

Try the interactive token-generator tool →