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 ↑- 1.
Python Software Foundation, "random — Generate pseudo-random numbers," docs.python.org, accessed July 2026. https://docs.python.org/3/library/random.html
- 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.
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.
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.
Wikipedia, "Mersenne Twister," en.wikipedia.org, accessed July 2026. https://en.wikipedia.org/wiki/Mersenne_Twister
The random module's Mersenne Twister generator has a fully reconstructable internal state once an attacker observes 624 consecutive 32-bit outputs, after which every subsequent value it produces becomes predictable. Passwords generated this way could theoretically be predicted by anyone who gathers enough prior output from the same generator instance.
secrets.choice() selects one item at a time from a character pool you define, letting you control exactly which characters appear. secrets.token_urlsafe() generates a fixed number of random bytes and encodes them as a URL-safe string, giving you precise control over entropy in bits rather than over the specific character set.
At the 94-character pool from the official recipe, 16 characters yields roughly 105 bits and 20 characters yields roughly 131 bits, both comfortably above the 98.5 bits a 15-character NIST-recommended password reaches at the full printable ASCII pool.
No. secrets.choice() is implemented to sample uniformly across the sequence you pass it, avoiding the modulo bias that a naive implementation using division or remainder operations on raw random bytes can introduce when the pool size does not evenly divide the random source's range.
The secrets module only generates and compares secrets securely, using secrets.compare_digest() for constant-time comparison; it does not include strength analysis. Pairing a secrets-generated password with CapyToolkit's password entropy checker, which analyzes entropy and checks for common patterns, covers both halves of the process.
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
- crypto.randomInt(min, max) bounded integers, uses rejection sampling to avoid modulo bias
- crypto.randomBytes(32) 256 bits of entropy as a raw byte buffer
- crypto.randomUUID() 122 bits of actual randomness (6 of 128 bits are fixed by the UUID format)
- Never use Math.random() — not cryptographically secure per Node's own docs
Verify with the Password Entropy Analyser tool.
Try it in the tool ↑- 1.
OpenJS Foundation, "Crypto | Node.js v22 Documentation," nodejs.org, accessed July 2026. https://nodejs.org/api/crypto.html
- 2.
NIST, "Authenticators," SP 800-63-4, pages.nist.gov, accessed July 2026. https://pages.nist.gov/800-63-4/sp800-63b/authenticators/
- 3.
OWASP, "Password Storage Cheat Sheet," cheatsheetseries.owasp.org, accessed July 2026. https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
- 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.
MDN Web Docs, "Web Crypto API," developer.mozilla.org, accessed July 2026. https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API
No. Node's documentation explicitly states that Math.random() does not use a cryptographically secure algorithm and should not be used for anything cryptographic, including password or token generation. Use crypto.randomInt(), crypto.randomBytes(), or crypto.randomUUID() instead for any security-sensitive value.
Modulo bias happens when mapping a random byte onto a pool size that does not evenly divide the byte's range, causing some values to be selected slightly more often than others. crypto.randomInt() avoids this by using rejection sampling internally, discarding and re-drawing any value that would introduce that bias.
A version-4 UUID provides 122 bits of actual randomness, not the full 128 bits its length might suggest, because 6 bits are fixed by the UUID version and variant fields defined in the specification. CapyToolkit's password entropy checker scores a generated token by that same bit count, so a UUID and a randomBytes() output land in the same high-entropy tier despite looking nothing alike. That is still far more than enough entropy for a session identifier or token use case.
Use crypto.randomInt() when you need to select characters from a specific, human-readable pool like letters, digits, and symbols. Use crypto.randomBytes() when you want raw entropy encoded as a string, such as base64url, without needing the result to draw from a particular character set.
No, crypto.randomInt() is specific to Node.js. In a browser, the equivalent secure source is crypto.getRandomValues(), part of the Web Crypto API, which fills a typed array with cryptographically secure random values rather than returning a single bounded integer directly.
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 ↑- 1.
Microsoft, "RandomNumberGenerator Class," learn.microsoft.com, accessed July 2026. https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator
- 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.
OWASP, "Password Storage Cheat Sheet," cheatsheetseries.owasp.org, accessed July 2026. https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
- 4.
Wikipedia, "Cryptographically secure pseudorandom number generator," en.wikipedia.org, accessed July 2026. https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator
- 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
No. Get-Random relies on general-purpose .NET random number facilities designed for speed and statistical distribution in non-adversarial contexts, not for resisting prediction. For anything security-sensitive, including passwords and tokens, use [System.Security.Cryptography.RandomNumberGenerator] instead.
No. RandomNumberGenerator is part of the .NET base class library, and both Windows PowerShell 5.1 and PowerShell 7+ can call it directly using the [System.Security.Cryptography.RandomNumberGenerator] type accelerator syntax without any additional module installation.
RNGCryptoServiceProvider still exists as a derived class, but Microsoft's documentation recommends calling the static members of RandomNumberGenerator directly instead, since it works consistently across platforms without depending on the specific provider RNGCryptoServiceProvider was originally built around.
At a 94-character pool, 16 characters yields roughly 105 bits of entropy and 20 characters yields roughly 131 bits, both well above the 98.5 bits a 15-character password reaches at the full printable ASCII pool referenced elsewhere in NIST's current password guidance. CapyToolkit's password entropy checker scores a GetString()-generated password using that same bit-based math, regardless of which language or command produced it.
Yes. The same GetString() or GetInt32() pattern works for any character-based secret, and GetBytes() works for raw token generation. Adjust the character pool and length to match whatever the target system requires, such as the 8-to-63 character range WPA2 passphrases accept.
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
- tr -dc '<charset>' < /dev/urandom | head -c N filters a uniform byte stream by inclusion — no modulo bias
- openssl rand -base64 16 128 bits of entropy in one call, no filtering needed
- /dev/urandom vs /dev/random functionally identical today — both backed by the same kernel CSPRNG
- Never use $RANDOM — a simple linear generator, not built to resist prediction
Verify with the Password Entropy Analyser tool.
Try it in the tool ↑- 1.
Linux man-pages project, "random(4)," man7.org, accessed July 2026. https://man7.org/linux/man-pages/man4/random.4.html
- 2.
OpenSSL, "rand(1) — generate random numbers," openssl.org, accessed July 2026. https://www.openssl.org/docs/man3.0/man1/rand.html
- 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.
Wikipedia, "/dev/random," en.wikipedia.org, accessed July 2026. https://en.wikipedia.org/wiki//dev/random
- 5.
Linux man-pages project, "tr(1)," man7.org, accessed July 2026. https://man7.org/linux/man-pages/man1/tr.1.html
Yes. The Linux kernel's random(4) manual page documents /dev/urandom as a cryptographically secure pseudorandom number generator. CapyToolkit's password entropy checker scores a tr-generated or openssl-generated password identically, since both idioms draw from that same underlying secure source regardless of which shell command produced the final string. Since the underlying source was unified with /dev/random, the two produce output of equivalent quality once the kernel's generator has initialized, which happens once, early at boot.
No, not on a system that has already booted. /dev/random can still block briefly during very early boot before its generator initializes, while /dev/urandom never blocks. On a running system, both draw from the same underlying cryptographically secure source, so there is no security benefit to preferring /dev/random.
-d tells tr to delete characters, and -c complements the specified set, so tr -dc 'A-Za-z0-9' deletes every character that is not a letter or digit, keeping only the ones that are. Combined with piping from /dev/urandom, this filters the random byte stream down to just the character pool you want.
No. $RANDOM is a simple pseudorandom generator built for scripting convenience, with a limited range and no cryptographic security guarantee. For any password or secret that needs to resist prediction, use /dev/urandom or openssl rand instead, even in a quick one-off script.
Adjust the character class passed to tr -dc, for example tr -dc 'A-Za-z0-9' to keep only letters and digits. This reduces the pool from 94 to 62 characters, so you may want to increase the length to compensate for the lower per-character entropy.