How Do You Generate a Cryptographically Safe Random String?

secure random string — Chunky Munster

If you need a token, reset code, session ID, or invite link, a secure random string has to come from a cryptographic source, not Math.random(). For a quick browser-based option, try our free cryptographically safe random string tool and skip the usual foot-guns.

What Makes a String Secure

A secure random string is unpredictable to an attacker, not just “looks random” to a human. That means the underlying bytes must come from a cryptographically secure random number generator, then be encoded into a string you can actually use.

The important part is the source. If your string protects authentication, account recovery, API access, or one-time use links, it needs real entropy. A pretty shuffle of characters is not enough if someone can predict the next value.

In browsers, the right primitive is crypto.getRandomValues(). In Node.js, use crypto.randomBytes(). Both are designed for security-sensitive work, which is exactly why they exist.

Why Math.random() Is Not Enough

Math.random() is fine for UI flourishes, shuffling a local list, or picking a fake test colour. It is not fine for anything an attacker might guess, brute force, or replay.

The problem is not that the numbers are always identical. The problem is that pseudo-random generators are built for convenience and speed, not secrecy. If the generator can be modeled, seeded, or narrowed, your “random” string becomes a weak link.

A common mistake is to do this:

const token = Math.random().toString(36).slice(2, 10);

That looks tidy, but it is short, low-entropy, and derived from a non-cryptographic source. Hashing a weak value does not magically make it secure. It just gives you a different weak value.

If you want a broader refresher on where random values go wrong in browser code, our guide on how computers generate random numbers and whether they are really random is the right rabbit hole.

The Practical Recipe

The usual pattern is simple: generate secure bytes, then encode them in a format that fits the job. Common encodings are hex, base64url, or a custom character set if you need human-friendly output.

Hex is easy to read and debug, but it is longer than necessary. Base64url is compact and URL-safe. If you need codes humans will type, you may want to avoid ambiguous characters like 0, O, 1, l, and I.

Browser example with hex:

function secureTokenHex(byteLength = 16) {
  const bytes = new Uint8Array(byteLength);
  crypto.getRandomValues(bytes);
  return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
}

const token = secureTokenHex(16); // 32 hex chars

Node.js example with base64url:

import { randomBytes } from 'node:crypto';

const token = randomBytes(32)
  .toString('base64')
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=+$/g, '');

The output format should match the use case. A password reset token in a URL wants URL-safe characters. A database API key can usually be longer and less human-friendly. A coupon code might need a constrained alphabet so people can read or enter it without yelling at the keyboard.

Pick the Right Length

Security is not just about where the randomness comes from. Length matters too. A secure generator can still produce a string that is too short to resist guessing.

For machine-to-machine secrets, longer is usually better. Many teams use at least 16 bytes of entropy for tokens, and more for long-lived or high-value credentials. The exact number depends on your threat model, but “eight characters because it fit in the UI” is usually a bad sign.

For short-lived invitation codes, you may be balancing usability and risk. A 6-digit code is easy to type, but it is also small enough to brute force unless you rate-limit and expire it aggressively. If you do go short, add controls around it.

Encoding Choices Change the Shape of the Problem

Raw random bytes are great for machines, awkward for humans. Encoding turns those bytes into something you can store, copy, or embed in a URL.

Hex uses only 0-9a-f and is easy to inspect. Base64 packs more entropy into fewer characters, but standard base64 includes +, /, and padding characters that can be annoying in URLs. Base64url trims that pain.

If you need a string with a custom alphabet, generate bytes first and map them into your allowed character set carefully. Do not use a naive random % alphabetLength approach unless you understand modulo bias and are okay with uneven distribution. For a simple overview of encodings that often show up in these workflows, see why base64 is everywhere and what it actually does.

Where Developers Usually Get It Wrong

The most common failure mode is treating randomness like formatting. Developers generate a string, trim it, uppercase it, hash it, or slice it until it looks “right,” then assume the security part is handled. It usually is not.

Another mistake is reusing the same token across systems. A password reset token should not also be an invitation code, and a session secret should not be reused as a CSRF token. Different uses deserve different lifetimes and different handling.

Also watch storage. If the value needs to be checked later, store a hash of the token rather than the token itself when possible. That way, a database leak does not instantly expose every live secret.

Security tokens should be generated once, stored carefully, compared in constant time where appropriate, and expired fast. Randomness is only the first step.

A Worked Example

Say you want to issue password reset links. A weak approach might use a short, guessable value derived from Math.random(). A stronger approach uses secure bytes, encodes them safely, stores only a hash, and sets an expiry.

Before:

// weak, predictable enough to be dangerous
const token = Math.random().toString(36).slice(2, 10);

await db.insert({
  userId,
  resetToken: token,
  expiresAt: Date.now() + 15 * 60 * 1000
});

After:

import { randomBytes, createHash } from 'node:crypto';

const rawToken = randomBytes(32).toString('base64url');
const tokenHash = createHash('sha256').update(rawToken).digest('hex');

await db.insert({
  userId,
  resetTokenHash: tokenHash,
  expiresAt: Date.now() + 15 * 60 * 1000
});

// email rawToken in the reset link, then discard it

That version does a few things better. It uses a cryptographically secure source, produces a URL-safe string, and avoids storing the live secret in plain form. If the database leaks, the attacker does not get the reset token for free.

For browser-side generation, the pattern is similar, just swap in crypto.getRandomValues() and your preferred encoding. If you only need to generate a one-off value while testing, the tool is faster than wiring up a mini crypto script every time.

Frequently Asked Questions

Is Math.random() safe for passwords or tokens?

No. It is fine for non-security tasks, but it is not designed to resist prediction or attack. If the value protects access, use crypto.getRandomValues() in the browser or crypto.randomBytes() in Node.

What is the best format for a secure random string?

Base64url is a solid default for many web workflows because it is compact and URL-safe. Hex is simpler to inspect and debug, but it takes more characters to represent the same entropy. For human entry, use a restricted alphabet that avoids confusing characters.

How long should a secure token be?

There is no single magic number, but longer is safer. For many authentication flows, 16 to 32 bytes of entropy is a common range. Short codes can work for convenience, but they need expiry, rate limiting, and tight scope.

Should I store the token itself in my database?

Usually not, if you can avoid it. Storing a hash of the token means a database leak does not automatically expose every live secret. Compare the presented token to the stored hash, and expire it as soon as it is used.

The Bottom Line

If a string matters to security, generate it from a cryptographic source and encode it into the format your app needs. Skip Math.random(), be honest about length, and treat human-friendly formatting as a separate problem from entropy.

If you want a quick value without writing code, use the cryptographically safe random string tool. If you are wiring this into an app, generate the token once, store it carefully, and make sure the rest of the flow does not undo the good work.

// try the tool
try our free cryptographically safe random string tool →
// related reading
← all posts