Safeguard
Application Security•

Which random function should you use for secrets in Elixir?

Use :crypto.strong_rand_bytes for tokens and keys, never :rand or Enum.random. Plus constant time comparison and PBKDF2 basics from Erlang crypto.

Safeguard Security Team
4 min read

For tokens, session ids, password reset codes and keys in Elixir, use :crypto.strong_rand_bytes/1 from Erlang's crypto module. Do not use :rand or Enum.random, which are statistical generators and not cryptographically strong. Compare secrets with :crypto.hash_equals/2.

Why does the choice of generator matter?

CWE-338 names the weakness: a pseudo random number generator used in a security context, where the algorithm is not cryptographically strong. Attackers who observe some outputs may predict later ones. A password reset token that comes from a predictable generator is an account takeover waiting for a patient attacker.

Erlang's own rand module documentation is explicit. Its generators are not cryptographically strong, and for that need it points to the crypto module. Elixir's Enum.random and :rand.uniform sit on top of this module, so they inherit the limit.

What should you call instead?

The crypto application documents strong_rand_bytes(N), which generates N random bytes with uniformly distributed values, and strong_rand_range/1 for a random integer in a range. The rand docs add that crypto:strong_rand_bytes/1 is "cryptographically secure" and describes it as pretty fast on modern systems.

A constructed example for a reset token:

def new_token do
  :crypto.strong_rand_bytes(32)
  |> Base.url_encode64(padding: false)
end

That gives 256 bits of randomness encoded as URL safe text. Store only a hash of it in the database, so a database leak does not expose live tokens.

Never build tokens from a timestamp, a counter, a user id or :rand, even mixed together with other values.

How should you compare secrets?

A normal equality check may stop at the first different byte, which can leak timing information. The crypto documentation lists hash_equals(BinA, BinB), which compares two binaries in constant time, such as results of HMAC computations. Use it when comparing tokens, signatures or MACs.

Note that comparing the hashes of two secrets still needs the constant time function when the attacker controls one side and can measure response time.

What about passwords and keys?

For passwords, use a password hashing library built for it, such as an Argon2, bcrypt or PBKDF2 package from Hex, and read its parameters guidance. Erlang's crypto module itself provides pbkdf2_hmac(Digest, Pass, Salt, Iter, KeyLen), PKCS #5 PBKDF2 combined with HMAC, for key derivation. If you call it directly, generate a unique random salt per password with strong_rand_bytes, and choose the iteration count from current guidance such as the OWASP password storage cheat sheet. We did not fetch that sheet for this post, so check its present numbers.

Do not store keys in module attributes or source control. Load them at runtime from your secrets manager or release configuration.

Where do mistakes hide in Phoenix projects?

  • Enum.random/1 for invite codes, API keys or coupon codes.
  • :rand.seed calls with a fixed seed in tests that leak into runtime config.
  • Process or timestamp based unique ids used as secrets, which are identifiers and not secrets.
  • String == for token comparison.
  • Long lived signing salts copied between environments.

Search the codebase for Enum.random, :rand., :random. and Ecto.UUID.generate and decide for each whether the value is a secret. A UUID is an identifier. Treat it as guessable unless you have confirmed how your library generates it.

What to do

  1. Search for Enum.random, :rand. and :random. near anything named token, key, code or secret.
  2. Replace with :crypto.strong_rand_bytes/1, sized at 16 bytes minimum, 32 for long lived secrets.
  3. Store only hashes of reset and API tokens.
  4. Compare with :crypto.hash_equals/2.
  5. Use a maintained password hashing package, with a random salt per password.
  6. Keep keys in runtime configuration, not in source.
  7. Run mix deps.audit and mix sobelow in CI.

Safeguard's SAST can flag use of non cryptographic generators in security contexts, and dependency scanning covers the Hex packages in your lockfile.

Quick check

Is Enum.random fine for a coupon code?

If the code is a secret that grants value, no. Use crypto based randomness. For non secret shuffling, such as picking a sample, it is fine.

Is a UUID a secure token?

Not by default. It is an identifier. Confirm how it is generated, and use strong_rand_bytes for secrets.

Sources

Last reviewed: 18 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.