openssl rand — and the browser equivalent
What the command actually produces, and how to get the same bytes without it.
openssl rand -base64 32
openssl rand -hex 32
The 32 is bytes, not characters
This is the single thing that trips people up. openssl rand -base64 32 asks for 32 random bytes and then prints them in base64 — so the output is 44 characters (43 plus one = of padding), not 32. openssl rand -hex 32 asks for the same 32 bytes and prints 64 characters, because hex spends two characters per byte. Same entropy, same secret strength, different printed length:
32 bytes = 256 bits = 43 base64 characters = 64 hex characters
So if a tutorial tells you to run openssl rand -base64 32 and you get a 44-character string, nothing is wrong. And if you need "a 32-character secret", that is a different and much weaker request — 32 base64 characters is only 24 bytes.
-base64 or -hex?
Neither is more secure; they are two spellings of the same bytes. Pick hex when the value passes through something that might mangle punctuation — a shell command, a YAML file, a URL, an environment variable in a Dockerfile — because 0-9a-f is safe everywhere with no escaping. Pick base64 when length matters or when the thing you are configuring asks for it by name: Laravel's APP_KEY, Auth.js's AUTH_SECRET, and most JWT libraries expect base64.
One trap worth knowing: standard base64 contains + and /, which break URLs and some parsers. base64url swaps them for - and _. They are not interchangeable — feed base64url where standard base64 is expected and it decodes to different bytes, silently.
How many bytes should you ask for?
32. It matches AES-256 and the HMAC-SHA-256 that HS256 uses, and it is the number nearly every piece of documentation you will meet has settled on. 16 bytes (128 bits) is still computationally unbreakable and fine for salts and identifiers. Beyond 32, you are lengthening the string without strengthening the algorithm consuming it.
No OpenSSL? You don't need it
The command is not magic — it reads your operating system's CSPRNG and encodes the bytes. Your browser has the same CSPRNG behind crypto.getRandomValues(), which is what the two generators above call. The value you copy is the same class of secret, drawn from the same kind of source, and it never leaves this page. That matters most on Windows, where OpenSSL is not installed by default and the usual advice is to install a whole toolchain to produce one string. If you would rather stay in a terminal, these are the native equivalents:
PowerShell: [Convert]::ToBase64String((1..32|%{Get-Random -Max 256})) — convenient, but Get-Random is not a CSPRNG; prefer [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32).
Node: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Python: python -c "import secrets; print(secrets.token_hex(32))"
Does openssl rand -base64 32 give me a 32-character password?
No — it gives you 44 characters. The 32 is the number of random bytes; base64 then prints those 32 bytes as 43 characters plus one = of padding. If you actually need 32 characters of base64, that is only 24 bytes, which is a weaker secret than the command implies.
Should I use -base64 or -hex?
Neither is more secure — they are two spellings of the same random bytes. Use hex when the value passes through a shell, a URL or a config file, because 0-9a-f needs no escaping anywhere. Use base64 when length matters or when the thing you are configuring names it: Laravel's APP_KEY, Auth.js's AUTH_SECRET, most JWT libraries.
How do I generate this on Windows without OpenSSL?
You don't need OpenSSL at all — the command just reads your OS's CSPRNG and encodes the bytes, and this page does the same thing with your browser's. If you would rather stay in a terminal, use PowerShell's [System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32) — not Get-Random, which is not cryptographically secure.
Nothing leaves your browser
- Don't take our word for it — turn off your wifi. Every generator on this site keeps working with the network disconnected. That's the whole proof, and it takes five seconds.
- Every value comes from
crypto.getRandomValues()— the CSPRNG built into your browser, neverMath.random(). - Generated secrets are never transmitted, logged or stored: no server-side generation, no cookies, no localStorage.
- Verify it yourself in the network tab: after loading, the page only talks to our self-hosted, cookie-less analytics — which counts page views and which generator type gets copied, never any value.
- Strict Content-Security-Policy; no third-party script origins.