Secret Key Generator_
A key nobody can find is a key nobody can revoke. GitHub's ghp_, Stripe's sk_live_ and Slack's xoxb- are not branding — they are what lets a scanner recognise a credential the instant it lands in a public commit. A bare hexadecimal blob looks exactly like a hash, so gitleaks walks past it and the first anyone hears of the leak is the invoice.
The other half is arithmetic. "A 32-character key" is not a specification: in hexadecimal that is 128 bits, in base64url it is 192, and 32 bytes is 256. This shows the bits, offers the shapes real systems actually require, and formats the result for wherever it is going.
- Input
- A length and an alphabet, or one of eight presets taken from what a specific system documents that it needs. An optional prefix, kept verbatim.
- Output
- One key or a hundred, shown raw or wrapped for a .env file, a shell export, JSON, JavaScript, Python or YAML — with the quoting each of those actually requires.
- Processing
- Drawn in this tab from crypto.getRandomValues by rejection sampling, so every character of the alphabet is equally likely. No modulo reduction anywhere.
- Limits
- Up to 512 characters and 100 at a time. This makes keys; it cannot register them with the service that will accept them, and it cannot rotate the one you are replacing.
- Why the prefix is not counted in the bits
- A prefix is meant to be guessable — a scanner has to recognise it, and so does a person reading a log. Counting eight public characters towards the strength of a secret would overstate it by exactly the part that is not secret. So the figure shown is the entropy of the random portion alone.
A secret nobody can find is a secret nobody can revoke
Why real API keys start with letters that mean something
GitHub personal access tokens begin ghp_. Stripe uses sk_live_ and sk_test_. Slack bot tokens open with xoxb-. Those markers exist so that automated scanning can work: GitHub's secret scanning, gitleaks and truffleHog all match on known prefixes, and several providers have arrangements where a leaked key is revoked before its owner has noticed. A key that is forty random characters and nothing else is indistinguishable from a checksum, a hash, or a minified variable name, so none of that machinery fires. The prefix also earns its space in a log file, where sk_test_ versus sk_live_ is the difference between a wasted afternoon and a real charge.
Characters are not bits
Each character carries the base-two logarithm of the alphabet size: 4 bits for hexadecimal, 6 for base64, about 5.95 for plain alphanumerics. So the same "32 characters" is 128 bits in hex and 192 in base64url — and a request for "32 bytes" is 256, which in hexadecimal takes 64 characters to write down. Advice written in characters without naming the alphabet is ambiguous by a factor of two, and the direction people get it wrong in is always the same one: they type 32 into a length box because a tutorial said 32, and get half of what the tutorial meant.
The system consuming it decides the length
This is what makes a machine secret different from anything a person types. RFC 7518 requires an HMAC key at least as long as the hash output, so HS256 wants 256 bits and HS512 wants 512 — and most libraries accept a shorter one without complaint, silently giving you less than the algorithm name promises. Django's get_random_secret_key produces 50 characters from a specific 50-character alphabet. Rails emits 128 hexadecimal characters for secret_key_base because other keys are derived from it. None of these are preferences; they are requirements written down somewhere, and the presets above carry the reason with them.
The alphabet decides where it can be pasted
Ordinary base64 contains + and /, which need percent-encoding in a URL and can end a path segment — base64url swaps them for - and _ for exactly that reason. Worse and much less known: a dollar sign inside a .env file is interpolated by docker compose before your application starts, so the value that reaches the process is not the value in the file, and the failure looks like an authentication bug rather than a quoting one. Backslashes are escapes almost everywhere, quotes end values early, and a bare YAML scalar beginning with certain characters parses as something other than a string. An alphanumeric key survives all of it.
Rotating something nobody memorises
Because no human has to learn it, a machine secret can be replaced whenever you like — and the only reason it usually is not is that nothing was built to accept two at once. Design for it at the start: have the verifier try the current key and then the previous one, keep a key identifier alongside the value so you know which is in use, and rotation becomes a deployment rather than an outage. This is also the honest answer to a leaked key. You do not assess how bad it was; you replace it, and if replacing it is frightening then that, rather than the leak, is the thing to fix.
Pick the shape, read the bits, copy the format
- 01Choose the preset that matches what will consume the key. Hovering one shows the requirement it comes from.
- 02Check the bit count rather than the length. It is the number that means something, and it changes when you change the alphabet.
- 03Add a prefix for anything that could end up in a repository. It costs a few characters and makes the key findable by every scanner that exists.
- 04Pick where it is going. The .env, shell and YAML forms quote the value the way those formats actually need, which is not always the way that looks tidy.
A signing key for HS256 tokens
The tutorial said to use a secret string. The specification says at least 256 bits, and most libraries will not tell you when you are short.
JWT_SECRET=mysecretkey123
43 base64url chars — 258 bits RFC 7518: key ≥ hash output
An API key your scanners can see
Keys get committed. The question is not whether but whether anything notices, and a bare random string is invisible to every tool built to notice.
a3f9c1e8b7d2406f5a9c3e1b8d7f2064
sk_live_7Kd2mQ... Matched by GitHub secret scanning, gitleaks and truffleHog on the prefix.
A value that survives docker compose
The key works locally and fails in the container. Nothing in the logs mentions quoting, because the interpolation happened before the process started.
SECRET=aX$9pQ2z
SECRET=aX2z $9p was interpolated away. Alphanumeric keys cannot do this.
A key someone has to read out
Dictated over a call, or copied off a screen into a terminal. Zero against capital O and one against lowercase L are the errors that actually happen.
l0O1Il0O
129 bits with 0, O, I and l removed from the alphabet.
What each alphabet costs and buys
| Alphabet | Bits per character | 32 characters | Where it breaks |
|---|---|---|---|
Hexadecimal (16) | 4 | 128 bits | Nowhere. Verbose — a 256-bit key takes 64 characters. |
Base58 (58) | 5.86 | 187 bits | Nowhere. Drops 0, O, I and l so it can be read aloud. |
Alphanumeric (62) | 5.95 | 190 bits | Nowhere. The safe default for anything going in a config file. |
Base64url (64) | 6 | 192 bits | Nowhere. The - and _ are URL-safe by design. |
Base64 (64) | 6 | 192 bits | URLs — + and / need encoding, and / can end a path segment. |
Django (50) | 5.64 | 181 bits | Shells and .env files — contains $, ! and &. Matches the framework exactly. |
Printable ASCII (94) | 6.55 | 210 bits | Almost everywhere. Quotes, backslashes and dollar signs all mean something to some parser. |
The densest alphabet is rarely the right one. Going from alphanumeric to full ASCII buys about ten per cent more entropy per character and costs you the ability to paste the result anywhere without thinking.
Handling machine secrets
- Prefix anything that could reach a repository. It is the difference between a leak that is caught automatically and one that is caught by a stranger.
- Specify secrets in bits and bytes, never in characters. "32 bytes" is unambiguous; "32 characters" is a question about the alphabet.
- Prefer alphanumeric unless something requires otherwise. The entropy you give up is a rounding error next to an afternoon lost to a quoting bug.
- Build for two valid keys before you need to rotate. Accepting the current and previous value turns a rotation into a deployment instead of an outage.
- Never derive a key from something guessable. A hash of a project name or a timestamp has the entropy of its input, not the length of its output.
- Keep test and live keys visibly different. A prefix that says which is which prevents the one mistake that costs real money.
Where key generation goes wrong
A length is not a strength
Two keys of the same length can differ by a factor of two in entropy depending on the alphabet. Anything that reports characters and calls it a specification is leaving out the only number that matters.
An undersized HMAC key fails silently
RFC 7518 requires the key to be at least the hash output size, and most libraries accept a short one anyway. HS256 with a twelve-character secret produces perfectly valid tokens and roughly seventy bits of protection.
Config files edit your secrets
A dollar sign is interpolated by docker compose, a backslash is an escape almost everywhere, and an unquoted YAML value beginning with certain characters is not a string. The value that reaches your process may not be the value you generated.
Rotation is a design decision, not an incident response
If nothing can accept two keys at once, every rotation is downtime and every leak becomes an emergency. That property is decided when the verifier is written, long before anyone needs it.
A key in an environment variable is not encrypted
It is readable by anything in the process, printed by most crash reporters, and visible in a container inspection. That is usually acceptable — but it is a deliberate trade, not a protection.
Randomness, alphabets and encoding
- Randomness
- crypto.getRandomValues, drawn through the same rejection-sampled integer routine the other generators on this site use. Rejection rather than modulo: reducing a byte modulo 62 makes the first eight characters about four per cent more likely.
- Entropy
- Reported as length times log2 of the alphabet size, rounded down. The prefix contributes nothing and is excluded, since it is public by design.
- Alphabets
- Seven, from 16 to 94 characters. Base58 omits 0, O, I and l; base64url replaces + and / with - and _; the Django set reproduces get_random_secret_key exactly.
- HMAC keys
- RFC 7518 §3.2 requires a key of at least the hash output length — 256 bits for HS256, 384 for HS384, 512 for HS512. The presets clear those, which the test suite asserts arithmetically.
- Quoting
- The .env form single-quotes only when the value contains a character docker compose or a parser would alter; the shell form always quotes, because the value is not known in advance; JSON, JavaScript, Python and YAML are escaped and round-trip.
- Caps
- Up to 512 characters and 100 keys at a time.
- Network
- None from tool code. A test sweep calls every function this page uses with
fetchandXMLHttpRequestreplaced by stubs that throw, so a stray request fails the build instead of shipping. Disconnect from the network and the page still works.
Questions about generating secret keys
How long should a secret key be?
Ask what will consume it. An HS256 signing key needs 256 bits by specification, a Django SECRET_KEY is 50 characters from its own alphabet, and a Rails secret_key_base is 128 hexadecimal characters. Where nothing specifies, 128 bits is the floor and 256 is comfortable — and note that all of those are stated in bits or in an alphabet, never in bare characters.
What is the difference between a secret key and a password?
Nobody has to remember a secret key, so it can be far longer and completely random, and it can be replaced without asking anyone to learn a new one. It also sits behind no rate limiting: an attacker who captures something signed with it can try offline as fast as hardware allows, which is why the bar is higher than for a password.
Is 32 characters enough for a JWT secret?
It depends entirely on the alphabet, which is the problem with the way that advice is usually written. 32 hexadecimal characters are 128 bits — below what HS256 assumes. 43 base64url characters are 258 bits and clear it. RFC 7518 states the requirement in terms of the hash output size, not characters.
Why do API keys have prefixes like sk_live_ or ghp_?
So that automated scanners can recognise them. GitHub secret scanning, gitleaks and truffleHog all match known prefixes, and several providers revoke a leaked key automatically as a result. A prefix also tells a human reading a log which environment they are looking at, which is the difference between a test charge and a real one.
Should a secret key be base64 or hexadecimal?
Base64url if you want it compact and safe in a URL, hexadecimal if whatever consumes it expects hex — many libraries hand you hex by default. Avoid ordinary base64 where the value will appear in a URL, since + and / both need encoding. Where it is going into a config file, plain alphanumeric avoids every quoting problem at a cost of about one per cent of entropy per character.
Are these keys generated in my browser?
Yes. The work is JavaScript running in this tab. Every function it calls is covered by a test that stubs fetch and XMLHttpRequest to throw, so a request that slipped in would break the build rather than reach a server — and you can confirm it for yourself by disconnecting and carrying on.
How random are these keys?
They come from crypto.getRandomValues, the browser's cryptographic random source, selected by rejection sampling so every character is equally likely. Reducing a random byte modulo the alphabet size — the obvious approach, and a common one — skews the result whenever the alphabet does not divide 256.
How often should I rotate a secret key?
Often enough that you know rotation works. The interval matters less than the capability: if the verifier accepts both the current and the previous key, rotation is routine and a leak is inconvenient. If it accepts only one, every rotation is an outage and every leak is an emergency.
What should I do if a key was committed to a repository?
Revoke and replace it, then worry about the history. Rewriting the history does not help — the value was cloned, forked and indexed the moment it was pushed, and any copy is as good as the original. This is also why the prefix matters: it is what makes something notice before you do.
Can I use a UUID as a secret key?
A version 4 UUID carries 122 random bits, which is close to the 128-bit floor, so it is not absurd. It is a poor choice anyway: UUIDs are conventionally treated as identifiers and get logged, put in URLs and shown in interfaces precisely because they are assumed not to be secret.