Cryptography

Hex vs Base64 for JWT Secrets: Which Format Is Better?

7 min read
By
Hex vs Base64 for JWT Secrets: Which Format Is Better?

Photo by Lukas from Pexels

When you generate a JWT secret, you're generating random bytes. But you can't put raw bytes in a .env file, a YAML config, or a JSON secrets manager entry. You need to represent those bytes as a string, and the two most common encodings for that are hexadecimal (hex) and base64.

The choice between hex and base64 seems trivial, and in many ways it is. Both correctly represent your random bytes. Both are widely supported. Neither affects the security of the underlying secret. But the choice has practical implications for configuration size, human readability, library compatibility, and debugging that are worth understanding.

What Are You Actually Encoding?

First, let's be clear about what's happening. Your JWT secret is a sequence of random bytes. If you generate 32 random bytes, you have 256 bits of entropy, which is what you want for HS256.

Those 32 bytes can't be directly stored in most configuration systems because many bytes don't correspond to printable ASCII characters. Byte values 0-31 are control characters. Byte value 127 is DEL. Bytes 128-255 are outside the ASCII range entirely. Storing raw bytes in a text-based config file would corrupt the data.

So you encode the bytes as a text string. Hex represents each byte as two hexadecimal characters (0-9, a-f). Base64 represents every three bytes as four base64 characters (A-Z, a-z, 0-9, +, /). Both are lossless, reversible encodings that can represent any byte sequence as a printable string.

Hex: Simple, Readable, Verifiable

Hexadecimal encoding is the simplest option. Each byte becomes exactly two characters from the set 0-9 and a-f (or A-F). A 32-byte secret becomes a 64-character hex string.

Advantages of hex:

Simplicity is hex's main advantage. The encoding is trivial to understand: each pair of characters is one byte. There are no special characters, no padding, no variations. Every system that handles text can handle hex without issue.

Human readability is better than base64 for certain tasks. Hex strings use only 16 characters, all alphanumeric. They're easy to visually compare, easy to paste into any config system, and don't contain characters that might be interpreted specially by shells, YAML parsers, or JSON encoders.

Debugging is easier with hex. When comparing two secrets to check if they match, hex strings are straightforward. Each byte is independently represented, so you can spot differences at a glance. Base64 groups bytes, making visual comparison harder.

No special characters means no escaping issues. Hex strings never contain +, /, =, or any other character that might be misinterpreted by a URL parser, a shell, a YAML loader, or a JSON serializer. This eliminates a whole class of configuration bugs.

Disadvantages of hex:

Size is hex's main disadvantage. Hex encoding doubles the size of the data: 32 bytes become 64 characters. This isn't a problem for a single secret in a config file, but it's wasteful if you're storing many secrets or transmitting them over bandwidth-constrained channels.

Base64: Compact, Standard, Efficient

Base64 encoding is more space-efficient. It represents every three bytes as four characters, so a 32-byte secret becomes approximately 44 characters (including padding). That's about 33% smaller than hex's 64 characters.

Advantages of base64:

Compactness is base64's main advantage. It's 33% more space-efficient than hex, which matters when you're storing or transmitting large amounts of binary data. For a single JWT secret, the difference is negligible, but it's the standard encoding for larger cryptographic material like RSA private keys.

Base64 is the standard encoding for many cryptographic and web standards. JWT itself uses base64url (a URL-safe variant) for encoding headers and payloads. Many JWT libraries expect or produce base64-encoded secrets by default.

Disadvantages of base64:

Special characters can cause configuration issues. Standard base64 uses +, /, and = (for padding). These characters have special meanings in URLs (+ is a space, / is a path separator), YAML (certain contexts), and shell variables. If your config system doesn't handle these correctly, your secret can be silently corrupted.

Padding characters (=) at the end of base64 strings are sometimes stripped by overzealous parsers or accidentally removed during copy-paste. This can cause decoding failures that are hard to debug.

Visual comparison is harder. Base64 groups bytes into blocks, so a one-byte difference can change multiple characters in the encoded string. Spotting the difference between two base64 secrets by eye is much harder than with hex.

base64url is a variant that replaces + with - and / with _, making the output URL-safe. This is what JWT uses internally. If your library expects base64url and you provide standard base64, or vice versa, you can get subtle errors.

The Library Compatibility Question

The most important practical consideration is what your JWT library expects. Different libraries handle secret encoding differently:

Some libraries expect raw bytes. You decode the hex or base64 string to bytes before passing it to the library. In Node.js, this means Buffer.from(secret, 'hex') or Buffer.from(secret, 'base64').

Some libraries accept a string and use it directly as the secret bytes. In this case, you pass the hex or base64 string as-is, and the library uses the string's byte representation. This is where things get tricky: if your library treats the string as the raw secret bytes, using a 64-character hex string gives you a 64-byte secret (512 bits), not a 32-byte secret (256 bits). That's fine for security, but it's not what you might expect.

Some libraries accept both and auto-detect. These are the most convenient but also the most confusing, because the behavior might differ between versions.

The key takeaway: read your library's documentation. Understand whether it expects raw bytes or a string. If it expects raw bytes, decode your encoded secret first. If it expects a string, decide whether you want the encoded string itself to be the secret (which is fine, just be consistent) or whether you want the underlying random bytes to be the secret (in which case, decode before passing).

Which Should You Choose?

For most applications, the choice doesn't matter much from a security perspective. Both hex and base64 correctly represent your random bytes, and the entropy is the same either way. Here's our practical recommendation:

**Use hex if:** you want simplicity and debuggability. Hex is easier to read, compare, and paste into config files. It has no special characters that might cause encoding issues. For a single JWT secret in a .env file, hex is the safer, simpler choice.

**Use base64 if:** your library specifically expects it, you're working with other cryptographic material that's already base64-encoded, or you're in an environment where base64 is the standard. Just be aware of the special character issues and use base64url if the secret will appear in URLs.

**Use base64url if:** your secret will be transmitted in URLs, HTTP headers, or other contexts where + and / are problematic. base64url is the URL-safe variant and is what JWT itself uses for encoding.

Generating Your Secret

Regardless of which format you choose, the underlying bytes must be generated with a cryptographically secure random number generator. Never use Math.random(), a human-typed string, or any predictable source.

Our JWT Secret Generator generates cryptographically secure random bytes using the Web Crypto API and outputs them in both hex and base64url format. You can choose the format that works best for your configuration system and copy it directly.

For more on the broader practices around JWT secret management, see our JWT secret key best practices guide. The encoding is just the surface; the real security comes from using strong random bytes and managing them properly.

The Bottom Line

Hex vs base64 is not a security decision. It's a practical one. Both formats correctly represent your random bytes. The security comes from the entropy of those bytes, not the encoding.

Choose hex for simplicity and debuggability. Choose base64 for compactness and standards compatibility. Choose base64url for URL-safe contexts. And regardless of your choice, make sure you understand how your JWT library consumes the secret, so you're using the actual random bytes and not accidentally using the encoded string as a weak, human-readable secret.

Frequently Asked Questions

Does the encoding format affect JWT security?

No. The encoding (hex, base64, base64url) is just a representation of the underlying random bytes. The security comes from the entropy of those bytes, not the format they’re stored in. A 32-byte secret has 256 bits of entropy regardless of whether it’s represented as hex or base64.

Should I decode my hex/base64 secret before passing it to the JWT library?

It depends on the library. Some libraries expect raw bytes, in which case you should decode the hex or base64 string first. Others accept a string and use it directly as bytes. Check your library’s documentation. Using the wrong approach can silently weaken your secret by using the encoded string instead of the raw bytes.

What is base64url and why is it used for JWTs?

base64url is a variant of base64 that replaces + with - and / with _, and omits padding. This makes the output URL-safe, which is important because JWTs are often transmitted in URLs, headers, and cookies where + and / have special meanings. JWT itself uses base64url for encoding headers and payloads.

Can I use a plain text string as a JWT secret?

Technically yes, but it’s strongly discouraged. Plain text strings typed by humans have low entropy and are vulnerable to dictionary attacks. Always use a cryptographically generated random byte sequence, encoded as hex or base64, as your JWT secret. Generate one with our JWT Secret Generator.

Try NovelCrypt Tools

Experience military-grade encryption for your sensitive data. Create self-destructing messages, encrypt files, or explore our experimental lab tools.

Explore NovelCrypt