Cryptography

Encryption Key Generators: How to Create Secure Keys for Any Algorithm

8 min read
By
Encryption Key Generators: How to Create Secure Keys for Any Algorithm

NovelCrypt

Every encryption system has the same weakness: the key. AES-256 is mathematically unbreakable by brute force, but if your key was generated with Math.random() or derived from a predictable seed, an attacker does not need to break AES — they just need to guess your key. Key generation is the foundation of cryptographic security, and it is where implementation mistakes most commonly undermine otherwise sound systems.

What Encryption Keys Are

An encryption key is a piece of data that determines the output of a cryptographic algorithm. The same plaintext encrypted with the same algorithm but different keys produces different ciphertext. The security of an encrypted system depends on three things: the strength of the algorithm, the secrecy of the key, and the randomness of the key generation.

Of these three, key generation is the most frequently botched. Developers choose strong algorithms (AES-256, RSA-2048) and protect their keys carefully, but generate the keys using non-cryptographic random number generators. The result is keys that look random but are predictable, which means the encryption provides no real security against anyone who understands how the key was generated.

Why Key Generation Matters

A key is only as strong as its entropy — the amount of unpredictability it contains. A 256-bit AES key has 256 bits of entropy only if every one of those 256 bits was chosen randomly. If the key was generated by hashing a timestamp, the entropy is roughly 30 bits (the number of seconds in a year), regardless of the key length. If it was generated by Math.random(), the entropy depends on the PRNG state, which can be as low as 48 bits on some JavaScript engines.

Low-entropy keys are vulnerable to brute-force attacks not on the encryption algorithm, but on the key generation process. An attacker who knows you used Math.random() to generate a 256-bit AES key does not try all 2^256 keys. They try all possible Math.random() states, which is a vastly smaller search space.

What a CSPRNG Is

A CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) is a random number generator that meets two requirements:

Unpredictability — given the output so far, it is computationally infeasible to predict future output or reconstruct past output.

Backtracking resistance — if the internal state is compromised at some point in time, past outputs remain unrecoverable.

CSPRNGs gather entropy from environmental noise — hardware events, interrupt timings, disk seek times, thermal noise — and use it to seed a cryptographic algorithm that produces output indistinguishable from true randomness.

### CSPRNG Sources by Platform

| Platform | CSPRNG Source | How to Access | | --- | --- | --- | | Browser (JavaScript) | crypto.getRandomValues() | window.crypto.getRandomValues(array) | | Node.js | crypto.randomBytes() | require('crypto').randomBytes(n) | | Linux | /dev/urandom (or getrandom syscall) | read from /dev/urandom | | Windows | BCryptGenRandom | BCryptGenRandom(handle, buffer, flags) | | Python | secrets module | secrets.token_bytes(n) or secrets.token_hex(n) | | Go | crypto/rand | rand.Read(slice) |

In the browser, crypto.getRandomValues() is the correct way to generate random bytes for cryptographic purposes. It is available in all modern browsers and uses the operating system CSPRNG under the hood. For a detailed explanation of how it works, read our CSPRNG guide.

Key Length Recommendations by Algorithm

Different algorithms require different key lengths because their security scales differently. Symmetric algorithms use the key length directly. Asymmetric algorithms derive security from mathematical problems (factoring, discrete logarithm) that are harder to attack than brute force, so they need longer keys to achieve equivalent security.

### Symmetric Algorithms

| Algorithm | Recommended Key | Security Level | Notes | | --- | --- | --- | --- | | AES-128 | 128 bits (16 bytes) | 128 bits | Secure for most applications | | AES-256 | 256 bits (32 bytes) | 256 bits | Recommended for new systems | | ChaCha20 | 256 bits (32 bytes) | 256 bits | Alternative to AES | | HMAC-SHA256 | 256 bits (32 bytes) | 128 bits | Key should match hash output | | HMAC-SHA512 | 512 bits (64 bytes) | 256 bits | For high-security applications |

### Asymmetric Algorithms

| Algorithm | Recommended Key | Security Level | Notes | | --- | --- | --- | --- | | RSA | 2048 bits minimum, 3072+ preferred | 112 bits (2048), 128 bits (3072) | NIST SP 800-57 | | ECDSA (P-256) | 256 bits | 128 bits | Equivalent to RSA-3072 | | Ed25519 | 256 bits (fixed) | 128 bits | Modern default for signatures | | ECDH (X25519) | 256 bits (fixed) | 128 bits | Modern default for key exchange |

### Application-Specific Keys

| Use Case | Recommended | Generator | | --- | --- | --- | | JWT HS256 | 256+ bits | JWT secret generator | | JWT RS256 | 2048+ bit RSA key pair | RSA key generation | | Django SECRET_KEY | 50+ characters | Django key generator | | Flask SECRET_KEY | 32+ bytes | Same as JWT secret | | API tokens | 256+ bits | CSPRNG + base64 encoding | | Password salt | 128+ bits | CSPRNG |

Common Mistakes That Make Keys Vulnerable

### Using Math.random()

Math.random() in JavaScript is not a CSPRNG. It typically uses a xorshift128+ or similar PRNG with a seed derived from a low-entropy source. Its output is predictable if an attacker can observe enough outputs to infer the internal state. Using Math.random() for any cryptographic purpose — keys, tokens, nonces, salts — is a critical vulnerability.

The fix is simple: always use crypto.getRandomValues() in browsers and crypto.randomBytes() in Node.js. There is no scenario where Math.random() is acceptable for security.

### Predictable Seeds

Even when using a CSPRNG, seeding it with a predictable value undermines security. If you seed your CSPRNG with Date.now() or Math.random(), the output is no more random than the seed. Modern operating system CSPRNGs (dev/urandom, BCryptGenRandom) handle seeding automatically using kernel entropy sources — do not try to seed them yourself.

### Insufficient Entropy

A key generated on a freshly booted virtual machine or container may have low entropy because the system has not accumulated enough environmental noise. This was the root cause of numerous weak SSH key vulnerabilities in cloud environments, where VMs booted from identical snapshots and generated keys from the same initial entropy state. Modern systems mitigate this by blocking on /dev/random until sufficient entropy is available, but containers and embedded systems can still be vulnerable.

### Reusing Keys Across Systems

Using the same encryption key for multiple purposes or multiple systems increases the impact of a compromise. If one system is breached, all systems sharing the key are exposed. Generate separate keys for each application and each purpose. Key rotation practices, as described in our JWT secret rotation guide, apply to all cryptographic keys, not just JWTs.

### Deriving Keys from Passwords Without KDF

If you need to generate an encryption key from a user password, never hash the password directly with SHA-256 and use the result as a key. Passwords have low entropy (even strong passwords are typically 60-80 bits of entropy). Use a key derivation function like PBKDF2, bcrypt, scrypt, or Argon2id to slow down brute-force attempts. These functions add a work factor (iterations) and a salt, making it computationally expensive to try many passwords. Read our PBKDF2 vs bcrypt vs Argon2 comparison for guidance on choosing the right KDF.

Generating Keys with NovelCrypt Tools

NovelCrypt provides browser-based key generators that use crypto.getRandomValues() for all random output. No randomness is generated or sent to a server — everything happens in your browser.

### AES Keys

For AES-256, you need 32 bytes of random data. You can generate this using the AES text encryptor, which handles key generation as part of the encryption process. If you need a raw key for programmatic use, generate 32 random bytes and encode them as hex or base64.

### JWT Secrets

For JWT HS256, generate at least 32 bytes (256 bits) of random data and encode it as base64. The JWT secret generator does this automatically and produces a ready-to-use secret string. For RS256, generate an RSA key pair of at least 2048 bits. Read our JWT secret key best practices for detailed guidance on managing JWT secrets.

### Django SECRET_KEY

Django requires a SECRET_KEY for cryptographic signing (session tokens, password reset tokens, CSRF tokens). It should be at least 50 characters of random data. The Django key generator produces a Django-compatible SECRET_KEY using crypto.getRandomValues(). Read our Django secret key explainer for why this key matters and how to manage it.

### HMAC Keys

For HMAC-SHA256, generate 32 bytes. For HMAC-SHA512, generate 64 bytes. The HMAC generator handles key generation as part of the HMAC computation. Read our HMAC-SHA256 vs SHA-512 comparison for guidance on which to use.

Key Storage Best Practices

Generating a secure key is only half the battle. Storing it securely is equally important:

Never hardcode keys in source code. Source code is committed to version control, shared with team members, and sometimes leaked. Use environment variables or a secrets management system (AWS Secrets Manager, HashiCorp Vault, Doppler) instead. Read our secret key storage guide for practical patterns.

Use different keys for different environments. Development, staging, and production should each have separate keys. A leaked development key should not compromise production.

Rotate keys periodically. Even if a key is not known to be compromised, regular rotation limits the blast radius of a future breach. Implement zero-downtime rotation for keys that require continuous availability.

Protect keys at rest. If you store keys on disk (for development), encrypt them or use OS-level key stores (macOS Keychain, Windows Credential Manager, Linux kernel keyring). In production, use a secrets manager rather than filesystem storage.

Restrict key access. Not every developer needs production keys. Not every service needs every key. Follow the principle of least privilege — grant key access only to the systems and people who need it.

The Bottom Line

Key generation is where cryptographic systems most commonly fail. The algorithms are sound, the implementations are usually correct, but the keys are generated with non-cryptographic randomness that makes them predictable. Use a CSPRNG for every key, token, and nonce. Match your key length to your algorithm. Never use Math.random() for security. And for practical key generation across AES, JWT, Django, and HMAC, use the browser-based generators that NovelCrypt provides — they use the correct CSPRNG and produce keys at the correct length for each algorithm.

Frequently Asked Questions

How long should my encryption key be?

It depends on the algorithm. For AES, use 256 bits (32 bytes). For RSA, use at least 2048 bits, preferably 3072 or 4096 bits for long-term security. For HMAC-SHA256, use 256 bits. For JWT HS256 secrets, use at least 256 bits (32 bytes) of random data. For Ed25519, the key is 256 bits by design. Longer is not always better — focus on using the recommended length for your algorithm with a properly random key.

What is a CSPRNG?

A CSPRNG (Cryptographically Secure Pseudo-Random Number Generator) is a random number generator designed to produce output that is computationally indistinguishable from true randomness and unpredictable to an attacker. Examples include /dev/urandom on Linux, BCryptGenRandom on Windows, and crypto.getRandomValues() in browsers. Unlike Math.random(), a CSPRNG is safe for generating encryption keys, tokens, and nonces.

Is Math.random() safe for encryption keys?

No. Math.random() is not cryptographically secure. Its output is predictable if an attacker can infer the internal state, and it was never designed for security purposes. Using Math.random() to generate encryption keys, tokens, or salts is a critical vulnerability. Always use crypto.getRandomValues() in browsers or the crypto module in Node.js for cryptographic randomness.

How do I generate a JWT secret?

Use a CSPRNG to generate at least 256 bits (32 bytes) of random data, then encode it as base64 or hex for storage. For HS256, the secret should be at least 32 bytes. For RS256, generate a 2048-bit or larger RSA key pair. Never use a human-readable password or a short string as a JWT secret — use a [JWT secret generator](/lab/jwt-secret-generator) that produces cryptographically random output.

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