Password Entropy Analyser: Code Examples

Calculate Shannon entropy, run offline zxcvbn pattern analysis, and see estimated GPU crack times across four hardware tiers — nothing leaves your browser.

ZERO UPLOAD · ALL LOCAL
  1. Type or paste a password into the input field — strength updates instantly as you type.
  2. Check the entropy bits and strength tier in the meter below the input.
  3. Review the crack time table to see how long each attack scenario would take against your password.
  4. Read the feedback panel for specific suggestions if zxcvbn detected patterns or weaknesses.
  5. Use the eye icon to unmask the password if you need to review what you typed.

Type or paste a password to analyse its strength.

Length
Character pool
Entropy
Pattern
Attack Scenarios GPU times assume raw brute-force of a stolen hash. Online times use pattern-aware estimation against a live service.
GPU — Fast Hash (NTLM, MD5) Est. crack time
RTX 4070 Ti Super 155 GH/s · 16 GB GDDR6X
RTX 5070 175 GH/s · 12 GB GDDR7
RTX 4090 300 GH/s · 24 GB GDDR6X
RTX 5090 410 GH/s · 32 GB GDDR7
GPU — Slow Hash (bcrypt cost 12 / Argon2id) Est. crack time
RTX 4070 Ti Super 740 H/s * · 16 GB GDDR6X
RTX 5070 670 H/s * · 12 GB GDDR7
RTX 4090 1,440 H/s * · 24 GB GDDR6X
RTX 5090 2,380 H/s * · 32 GB GDDR7
Online Attack Est. crack time
Throttled 100 guesses/hour — rate-limited login service
Unthrottled 10 guesses/second — no rate limiting

* bcrypt and Argon2id are deliberately slow password hashes, so their crack times depend entirely on the cost factor a site configures. These rates assume bcrypt at cost factor 12, a conservative legacy setting. OWASP's current guidance prefers Argon2id for new systems and lists bcrypt as a legacy fallback with a work factor of 10 or more. Published hashcat v6.2.6 benchmarks measure bcrypt at cost factor 5 (RTX 4090 at 184 kH/s, RTX 5090 at 305 kH/s); each step up the cost factor doubles the work, so cost 12 runs 128 times slower than the benchmark default. The RTX 4090 and RTX 5090 figures divide those benchmarks by 128, while the RTX 4070 Ti Super and RTX 5070 figures are scaled from their SM and core counts. Sources: hashcat RTX 4090, hashcat RTX 5090, and the OWASP Password Storage Cheat Sheet.

Python secrets Module Password Generation

random.choice() looks like the obvious tool for generating a password in Python, and it is exactly the wrong one. The random module runs on the Mersenne Twister algorithm, a fast, high-quality generator for simulations and games, but one whose internal state can be reconstructed from a large enough sample of its outputs, making it unsuitable for anything security-sensitive.1 Python's secrets module exists specifically to fix this, drawing from the operating system's cryptographically secure random source instead. It was added via PEP 506 for exactly this use case: generating tokens, passwords, and similar secrets that need to resist prediction. This page covers the official recipe, why it works, and a lower-level alternative for generating tokens by byte count rather than character count.

Why random is unsuitable, and what secrets does differently

The Mersenne Twister generator behind Python's random module is deterministic once seeded, and its full internal state can be recovered from just 624 consecutive 32-bit outputs, after which every future output becomes predictable.1 An attacker who observes enough of your generator's output, directly or indirectly, could reconstruct that state and predict subsequent passwords.

secrets.choice(), by contrast, draws from os.urandom() under the hood, which pulls from the operating system's cryptographically secure random number source rather than a deterministic algorithm.2 Because that source has no reconstructable internal state in the way Mersenne Twister does, there is no equivalent shortcut for an attacker to predict future output from past samples.

Why this gap stays invisible during normal development

This distinction rarely shows up as a bug during development, since both modules return plausible-looking random values in testing and neither one crashes or raises a warning when misused. The gap only matters once a real attacker has motive to reconstruct your generator's state, which is exactly the scenario password and token generation needs to be built to withstand from the start rather than patched after the fact.

The official recipe, character by character

Python's own documentation recommends this exact pattern for generating a random password: combine string.ascii_letters, string.digits, and string.punctuation into an alphabet, then call secrets.choice() once per character.2 That alphabet spans 94 characters (52 letters, 10 digits, 32 punctuation marks), giving log₂(94) ≈ 6.55 bits per character. Combining all three character classes into a single alphabet, rather than forcing one character from each category into a fixed position, is what lets every position in the resulting password draw independently and uniformly from the full 94-character set, which is exactly the property the entropy formula assumes.

Reading the entropy off the code directly

A 20-character password generated this way carries roughly 20 × 6.55 ≈ 131 bits of entropy, comfortably clearing every length threshold covered elsewhere on this tool. Because secrets.choice() samples uniformly at random from the alphabet on each call, the resulting entropy matches the theoretical maximum for that pool size and length exactly, something a hand-typed password essentially never achieves.

Shortening the password changes that figure directly: the same 94-character alphabet at 16 characters yields roughly 105 bits instead of 131, still comfortably above what a purely length-based comparison would suggest at a glance. Because every character contributes the same 6.55 bits regardless of its position in the string, the entropy math for any length reduces to a single multiplication rather than a lookup table or approximation.3

token_urlsafe as an alternative, and when to use it

secrets.token_urlsafe(nbytes) takes a different approach: instead of specifying a character pool and length, you specify a byte count directly, and the function returns a base64-encoded, URL-safe string representing that many random bytes.4 Calling secrets.token_urlsafe(16) generates exactly 128 bits of entropy, regardless of how many characters the resulting string happens to contain.

When byte-based tokens read better than character pools

This byte-based approach is cleaner for generating API keys, session tokens, or password-reset links, where you want a guaranteed entropy figure without reasoning about a character pool's log₂ value. For a password meant to be typed by a human, the character-pool recipe above still produces a more readable result than base64's mixed-case-plus-symbols output. secrets.token_hex(nbytes) offers a third option, returning the same underlying randomness encoded as a hexadecimal string, which some systems prefer when the token needs to avoid punctuation entirely. All three functions, choice(), token_urlsafe(), and token_hex(), draw from the same underlying secure source, so picking between them is purely a matter of output format rather than security strength.5

When to use this

Use secrets.choice() with the official recipe when generating a password meant to be typed or displayed to a user, and reach for secrets.token_urlsafe() or secrets.token_hex() when generating an API key, session token, or any secret consumed programmatically rather than typed by hand.

Notes

Never use the random module for anything security-sensitive, including password reset tokens, API keys, or session identifiers, since its Mersenne Twister generator is predictable given enough observed output. The secrets module, added specifically for this purpose via PEP 506, is the standard library's correct tool for any value that needs to resist an attacker's prediction, not just passwords.

Examples

Generating a 20-character password with the official recipe

import string
import secrets

alphabet = string.ascii_letters + string.digits + string.punctuation
password = ''.join(secrets.choice(alphabet) for i in range(20))

Produces roughly 131 bits of entropy from a 94-character pool, drawn from a cryptographically secure source.

Generating a 128-bit URL-safe token

import secrets

token = secrets.token_urlsafe(16)

The byte count (16) maps directly to entropy (128 bits), independent of the resulting string's character length.

Try in the tool

Python's secrets module functions

  • secrets.choice(alphabet) uniform selection from a character pool — the official password recipe
  • secrets.token_urlsafe(nbytes) base64, URL-safe — byte count maps directly to entropy
  • secrets.token_hex(nbytes) hex-encoded, avoids punctuation entirely
  • Never use the random module — Mersenne Twister state is reconstructable from 624 outputs

Verify with the Password Entropy Analyser tool.

Try it in the tool ↑
Sources
  1. 1.

    Python Software Foundation, "random — Generate pseudo-random numbers," docs.python.org, accessed July 2026. https://docs.python.org/3/library/random.html

  2. 2.

    Python Software Foundation, "secrets — Generate secure random numbers for managing secrets," docs.python.org, accessed July 2026. https://docs.python.org/3/library/secrets.html

  3. 3.

    Python Software Foundation, "PEP 506 -- Adding A Secrets Module To The Standard Library," peps.python.org, accessed July 2026. https://peps.python.org/pep-0506/

  4. 4.

    NIST, "Strength of Memorized Secrets (Appendix A)," SP 800-63B, github.com/usnistgov, accessed July 2026. https://github.com/usnistgov/800-63-3/blob/nist-pages/sp800-63b/appA_memorized.md

  5. 5.

    Wikipedia, "Mersenne Twister," en.wikipedia.org, accessed July 2026. https://en.wikipedia.org/wiki/Mersenne_Twister

FAQ

Node.js crypto Module Password Generation

Node.js ships two separate random number facilities, and only one of them belongs anywhere near password generation. Math.random() runs on an algorithm not specified by ECMAScript and not designed to resist prediction, making it unsuitable for anything security-sensitive. The built-in crypto module solves this with functions backed by the operating system's cryptographically secure random source: crypto.randomInt() for bounded integers, crypto.randomBytes() for raw byte buffers, and crypto.randomUUID() for identifier-style tokens.1 This page covers the exact pattern for generating a character-based password with crypto.randomInt(), why it avoids a subtle bias problem that naive implementations fall into, and when randomBytes() or randomUUID() fit better instead.

Math.random() vs crypto.randomInt(): what changes under the hood

V8, the JavaScript engine behind Node.js, implements Math.random() using an algorithm optimized for speed and statistical distribution in non-adversarial contexts, not for resistance to prediction. Node's own documentation is explicit that values generated by Math.random() do not use a cryptographically secure algorithm and should not be used for cryptographic purposes.1 That design choice makes sense for shuffling an array or picking a random UI animation, but it becomes a liability the moment the same generator is asked to produce a value meant to resist a determined, technically capable attacker.

How crypto.randomInt() guarantees uniform selection

crypto.randomInt(min, max) instead draws from the operating system's CSPRNG and applies rejection sampling internally to guarantee a uniform distribution across the requested range.2 Consequently, every integer in that range is equally likely to be returned, a property Math.random() based approaches do not guarantee once you start mapping its output onto an arbitrary-sized character pool. That guarantee holds regardless of how oddly sized the character pool is, since rejection sampling adapts to any pool length rather than assuming a power-of-two size the way a naive bitmask approach would.

The exact code pattern, and why rejection sampling matters

Building a password with crypto.randomInt() means calling it once per character position, using the character pool's length as the exclusive upper bound, then indexing into the pool with the result. Because crypto.randomInt() internally rejects and re-draws any raw random value that would otherwise skew the distribution, the character selected at each position is genuinely uniform across the full pool, not subtly biased toward lower indices.3

Why naive modulo mapping fails here

A common but flawed shortcut takes a random byte and applies the modulo operator against the pool size directly. When the pool size does not evenly divide the byte's range (256 values for a single byte), certain characters near the start of the pool end up very slightly more likely to be selected than others. crypto.randomInt() exists specifically to remove this failure mode without requiring you to reason about it yourself.

The bias from naive modulo mapping is small in any single character, often a fraction of a percent, but it compounds across an entire password and across every password a system generates. At scale, that compounding is exactly the kind of statistical weakness a determined attacker with enough sample passwords could eventually exploit, which is why the standard library handles it rather than leaving it to individual implementations.

randomBytes() and randomUUID() for token-style secrets

For secrets consumed programmatically rather than typed by a person, crypto.randomBytes(32).toString('base64url') generates 256 bits of entropy directly, sidestepping the character-pool-and-length calculation entirely. crypto.randomUUID() offers a more specialized option: a version-4 UUID carries 122 bits of actual randomness, since 6 of its 128 bits are fixed by the UUID version and variant fields defined in the current UUID specification.4

Choosing between randomBytes() and randomUUID()

Both functions share the same underlying cryptographically secure source as crypto.randomInt(), so the choice between them comes down to the output format you need, a raw byte-derived string for maximum entropy density, or a UUID for a value that also needs to fit an identifier-shaped format other systems expect. crypto.randomUUID() has the added benefit of built-in browser support through the same name, so code written for a Node.js backend and a browser frontend can share the identical call, reducing the chance of a mismatch between server-side and client-side identifier generation logic.5

When to use this

Reach for crypto.randomInt() when generating a password meant to be typed or displayed to a person from a specific character set. Use crypto.randomBytes() or crypto.randomUUID() for API keys, session tokens, or database identifiers where the output does not need to resemble a human-typed password. In a browser environment, the equivalent secure source is crypto.getRandomValues() from the Web Crypto API, which provides the same cryptographically secure random values without a Node.js dependency.5

Notes

Never substitute Math.random() into any of these patterns, including in older code you might be refactoring; Node's own documentation explicitly warns against using it for cryptographic purposes. All three crypto functions covered here share the same underlying operating-system random source, so the choice between them is about output format and use case, not about relative security strength.

Examples

Generating a 16-character password from a custom pool

const crypto = require('node:crypto');

const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
const password = Array.from({ length: 16 }, () => alphabet[crypto.randomInt(alphabet.length)]).join('');

crypto.randomInt() applies rejection sampling internally, so every character in the pool has an equal chance of selection.

Generating a 256-bit token for programmatic use

const crypto = require('node:crypto');

const token = crypto.randomBytes(32).toString('base64url');

32 bytes maps directly to 256 bits of entropy, independent of the resulting string's character length.

Try in the tool

Node's crypto module functions

  • bounded integers, uses rejection sampling to avoid modulo bias
  • 256 bits of entropy as a raw byte buffer
  • 122 bits of actual randomness (6 of 128 bits are fixed by the UUID format)
  • Math.random() — not cryptographically secure per Node's own docs

Verify with the Password Entropy Analyser tool.

Try it in the tool ↑
Sources
  1. 1.

    OpenJS Foundation, "Crypto | Node.js v22 Documentation," nodejs.org, accessed July 2026. https://nodejs.org/api/crypto.html

  2. 2.

    NIST, "Authenticators," SP 800-63-4, pages.nist.gov, accessed July 2026. https://pages.nist.gov/800-63-4/sp800-63b/authenticators/

  3. 3.

    OWASP, "Password Storage Cheat Sheet," cheatsheetseries.owasp.org, accessed July 2026. https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html

  4. 4.

    K. Davis, B. Peabody, P. Leach, "Universally Unique IDentifiers (UUIDs)," RFC 9562, rfc-editor.org, May 2024. https://www.rfc-editor.org/rfc/rfc9562

  5. 5.

    MDN Web Docs, "Web Crypto API," developer.mozilla.org, accessed July 2026. https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API

FAQ

PowerShell Secure Password Generation

Get-Random is PowerShell's default random number cmdlet, and like most languages' default generators, it was never built to resist prediction. Under the hood, Get-Random relies on .NET's general-purpose random number facilities, the same category of generator that powers simulations and games rather than security-sensitive values. Because PowerShell runs on .NET, it has direct access to System.Security.Cryptography.RandomNumberGenerator, the class Microsoft's own documentation describes as the preferred way to generate random values, backed by a cryptographically strong source rather than a fast, predictable algorithm.1 This page covers the exact PowerShell syntax for calling that class directly, without needing an external module.

Why Get-Random doesn't belong in a password generator

Get-Random is convenient for shuffling arrays or picking a sample value, but it was designed for general-purpose randomness, not for resisting an attacker who might try to predict future output from past results. Using it to generate anything security-sensitive, a password, a token, a temporary secret, inherits that same weakness. That gap rarely surfaces in ordinary scripting, since Get-Random behaves indistinguishably from a secure generator until someone with the motive and technical skill to reconstruct its state actually tries.

The .NET type accelerator gives you a direct alternative

Because PowerShell can call .NET types directly using bracket syntax, you do not need an external module to reach a cryptographically secure generator. [System.Security.Cryptography.RandomNumberGenerator] exposes static methods callable straight from a PowerShell script, giving you the same underlying security guarantee available in compiled .NET applications, without requiring PowerShellGet, a NuGet feed, or any dependency beyond the runtime PowerShell already ships with.

This matters particularly in enterprise environments where installing third-party PowerShell modules requires approval, a signed package source, or a change request. Because RandomNumberGenerator ships as part of the runtime itself, a provisioning or password-rotation script can reach cryptographic-grade randomness without waiting on that approval process or depending on a module that could later be deprecated or removed from a gallery entirely.

Generating a password with RandomNumberGenerator.GetString()

The RandomNumberGenerator class includes a GetString() method built specifically for this use case: pass it a string of allowed characters and a desired length, and it returns a string populated with characters chosen at random from that set, drawn from the cryptographically secure source.1 This removes the need to write a manual character-selection loop yourself.2

How much entropy a 16-character GetString() password actually carries

For a 16-character password drawn from a 94-character pool (uppercase, lowercase, digits, and common symbols), GetString() produces roughly 105 bits of entropy, matching what the same pool and length would yield in any other language's equivalent secure-random implementation. That figure holds regardless of which specific 16 characters end up chosen, since every character drawn from the pool carries the same log₂(94) contribution to the total regardless of its position in the string.

GetString() also accepts a distinct character set for every call, which makes it straightforward to generate a batch of passwords that each satisfy a different target system's allowed character rules without writing separate logic for each one. That flexibility matters most for a script that provisions accounts across several systems in one run, since a single function call can satisfy a strict eight-character alphanumeric-only policy on one target and a looser ninety-plus character symbol-inclusive policy on another without branching logic.

GetInt32 for manual control, and GetBytes for raw entropy

On PowerShell versions where GetString() is unavailable, GetInt32(fromInclusive, toExclusive) provides the same underlying guarantee at a lower level: call it once per character position, using it as an index into your character array, and build the password one character at a time. Because GetInt32() specifically returns a cryptographically strong integer within the range you specify, this manual loop carries the same security property as the higher-level GetString() call.3

GetBytes() for raw token generation instead of character pools

For tokens rather than human-typed passwords, GetBytes(count) returns a byte array directly from the same secure source, letting you encode it as hex or base64 for use as an API key or session identifier without reasoning about a character pool at all. That same byte-first approach scales to any size token simply by changing the count argument, without needing to reason about character pools, log₂ math, or alphabet composition at all.

A 32-byte call to GetBytes() produces 256 bits of entropy in a single line, piped through [Convert]::ToBase64String() or a hex-formatting loop depending on what format the receiving system expects. This mirrors the byte-count-first approach other languages' secure random APIs take, letting a single mental model transfer across Python, Node.js, and PowerShell scripts alike. Keeping that mental model consistent across languages reduces the chance of accidentally falling back to a weaker generator when switching between scripting environments on a mixed-language automation team spanning several platforms.4

When to use this

Use [System.Security.Cryptography.RandomNumberGenerator] whenever a PowerShell script needs to generate a password, temporary credential, or secret token, particularly in provisioning or automation scripts where a weak or predictable generated value could become a long-lived security gap. The same CSPRNG-backed approach translates directly to Python's secrets module, Node.js crypto, and Bash /dev/urandom, so a cross-platform automation team can maintain consistent security posture across languages.45

Notes

Avoid Get-Random for anything security-sensitive, since it is built for general-purpose randomness rather than resistance to prediction. RandomNumberGenerator is available in Windows PowerShell 5.1 and PowerShell 7+ without installing any additional module, since it is part of the .NET base class library both editions run on and depend on for other cryptographic operations too.

Examples

Generating a 16-character password with GetString()

$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
$password = [System.Security.Cryptography.RandomNumberGenerator]::GetString($chars, 16)

GetString() draws each character from the cryptographically secure source, avoiding both Get-Random and manual loop logic.

Manual character selection with GetInt32() for older PowerShell versions

$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*'
$password = -join (1..16 | ForEach-Object { $chars[[System.Security.Cryptography.RandomNumberGenerator]::GetInt32(0, $chars.Length)] })

GetInt32(fromInclusive, toExclusive) returns a cryptographically strong integer within the requested range for each character position.

Try in the tool

PowerShell's RandomNumberGenerator methods

  • GetString(chars, length) draws each character from the cryptographically secure source
  • GetInt32(fromInclusive, toExclusive) manual per-character selection for older PowerShell versions
  • GetBytes(count) raw byte array for token-style secrets
  • Never use Get-Random — built for general-purpose randomness, not prediction resistance

Verify with the Password Entropy Analyser tool.

Try it in the tool ↑
Sources
  1. 1.

    Microsoft, "RandomNumberGenerator Class," learn.microsoft.com, accessed July 2026. https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator

  2. 2.

    NIST, "Strength of Memorized Secrets (Appendix A)," SP 800-63B, github.com/usnistgov, accessed July 2026. https://github.com/usnistgov/800-63-3/blob/nist-pages/sp800-63b/appA_memorized.md

  3. 3.

    OWASP, "Password Storage Cheat Sheet," cheatsheetseries.owasp.org, accessed July 2026. https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html

  4. 4.

    Wikipedia, "Cryptographically secure pseudorandom number generator," en.wikipedia.org, accessed July 2026. https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator

  5. 5.

    P. Leach, M. Mealling, R. Salz, "A Universally Unique IDentifier (UUID) URN Namespace," RFC 4122, rfc-editor.org, July 2005. https://www.rfc-editor.org/rfc/rfc4122

FAQ

Bash /dev/urandom Password Generation

'urandom' sounds like the weaker, unblessed sibling of /dev/random, and that reputation is years out of date. Both device files are documented as cryptographically secure pseudorandom number generators in the Linux kernel's random(4) manual page, and since the getrandom() system call unified their underlying source, the practical difference between them is limited almost entirely to boot-time blocking behavior rather than output quality.1 For a shell script generating a password, /dev/urandom is the standard, correct choice, not a compromise. This page covers why the two devices converged, the standard tr-based idiom for building a password from it, and a cleaner openssl-based alternative for token-style secrets.

Why urandom and random are functionally the same today

Historically, /dev/random would block, pausing your script, when the kernel judged its entropy pool too depleted to guarantee unpredictable output, while /dev/urandom never blocked but could theoretically return lower-quality output very early in a fresh boot before the pool was seeded. The random(4) manual page now documents both interfaces as backed by the same kernel cryptographically secure pseudorandom number generator once that generator has been initialized, which happens once, early in boot, on virtually any running system a script would realistically execute on.1

What actually differs between the two today

The remaining difference is narrow: /dev/random can still block briefly immediately after boot on some systems before the CSPRNG finishes initializing, while /dev/urandom never blocks at all. For a script running on an already-booted, already-initialized system, which describes the overwhelming majority of real-world password generation use cases, the two devices produce output of equivalent cryptographic quality.

That distinction only matters for code that might run during early boot, such as an init script or a container entrypoint executed before the system has finished starting. A password-generation script invoked interactively or from a cron job practically never runs in that narrow window, which is why the tr and openssl idioms covered below default to /dev/urandom without hesitation.2

The standard tr + /dev/urandom idiom, and its one real caveat

Piping /dev/urandom through tr -dc with an allowed character range, then truncating to a fixed length with head -c, is the idiom most shell scripts use to build a password: tr discards every byte that does not fall within the specified character set, passing through only bytes that do. Because the original byte stream from /dev/urandom is uniformly distributed, and tr filters by inclusion rather than remapping with a modulo operation, the surviving characters remain uniformly distributed across the allowed set, avoiding the modulo bias that a naive division-based approach would introduce.

The efficiency caveat, and why it rarely matters

The caveat is efficiency, not correctness: because tr discards every byte outside your chosen character range, generating a password from a narrow character set consumes noticeably more raw bytes from /dev/urandom than a purpose-built API call would need, though this has no practical impact for a single password-length request. /dev/urandom's kernel CSPRNG can supply that extra volume instantly regardless of how narrow the target alphabet is, so the inefficiency stays purely theoretical for any password short enough for a person to type.

openssl rand as a cleaner alternative for token-style secrets

openssl rand -base64 16 draws 16 bytes directly from OpenSSL's own random source, itself seeded from the same kernel CSPRNG, and encodes them as base64, giving you exactly 128 bits of entropy without needing to filter or truncate a byte stream at all. This mirrors the byte-count-first approach Python's secrets.token_urlsafe() and Node's crypto.randomBytes() both take, trading a human-readable character pool for a direct, unambiguous entropy figure.3

Choosing between tr and openssl rand

For a password meant to be typed by a person, the tr-based idiom above still produces a more readable result, since you control exactly which characters appear. For an API key, environment variable secret, or any value a script or service will consume programmatically, openssl rand is the simpler, more direct choice. Both idioms are portable across essentially every Linux distribution and macOS, since they depend only on tools that ship by default rather than a package you would need to install first. openssl itself ships preinstalled on nearly every server distribution in common use, which makes the rand subcommand a dependable fallback even on a minimal container image.4

When to use this

Use the tr plus /dev/urandom idiom in shell scripts and CLI one-liners when you need a human-typeable password from a specific character set. Reach for openssl rand when generating a token, API key, or secret that a program will consume directly, where a base64 or hex encoding is perfectly acceptable output. The same kernel CSPRNG backs both approaches, and it mirrors the secure random sources used by Python, Node.js, and PowerShell, so a cross-platform team can maintain consistent entropy guarantees across languages.45

Notes

Avoid $RANDOM, Bash's built-in pseudorandom variable, for anything security-sensitive; it is a simple linear generator intended for scripting convenience, not for resisting prediction. Both the tr-based idiom and openssl rand draw from cryptographically secure sources and are appropriate for generating real passwords and tokens in scripts.

Examples

Generating a 20-character password with tr and /dev/urandom

password=$(tr -dc 'A-Za-z0-9!@#$%^&*()_+' < /dev/urandom | head -c 20)
echo "$password"

tr filters the uniform byte stream by inclusion, which avoids the bias a modulo-based selection approach would introduce.

Generating a 128-bit base64 token with openssl

token=$(openssl rand -base64 16)
echo "$token"

16 bytes maps directly to 128 bits of entropy, independent of the resulting base64 string's character length.

Try in the tool

Bash secure-random idioms

  • filters a uniform byte stream by inclusion — no modulo bias
  • 128 bits of entropy in one call, no filtering needed
  • functionally identical today — both backed by the same kernel CSPRNG
  • $RANDOM — a simple linear generator, not built to resist prediction

Verify with the Password Entropy Analyser tool.

Try it in the tool ↑
Sources
  1. 1.

    Linux man-pages project, "random(4)," man7.org, accessed July 2026. https://man7.org/linux/man-pages/man4/random.4.html

  2. 2.

    OpenSSL, "rand(1) — generate random numbers," openssl.org, accessed July 2026. https://www.openssl.org/docs/man3.0/man1/rand.html

  3. 3.

    NIST, "Strength of Memorized Secrets (Appendix A)," SP 800-63B, github.com/usnistgov, accessed July 2026. https://github.com/usnistgov/800-63-3/blob/nist-pages/sp800-63b/appA_memorized.md

  4. 4.

    Wikipedia, "/dev/random," en.wikipedia.org, accessed July 2026. https://en.wikipedia.org/wiki//dev/random

  5. 5.

    Linux man-pages project, "tr(1)," man7.org, accessed July 2026. https://man7.org/linux/man-pages/man1/tr.1.html

FAQ