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/1for invite codes, API keys or coupon codes.:rand.seedcalls with a fixed seed in tests that leak into runtime config.Processor 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
- Search for
Enum.random,:rand.and:random.near anything named token, key, code or secret. - Replace with
:crypto.strong_rand_bytes/1, sized at 16 bytes minimum, 32 for long lived secrets. - Store only hashes of reset and API tokens.
- Compare with
:crypto.hash_equals/2. - Use a maintained password hashing package, with a random salt per password.
- Keep keys in runtime configuration, not in source.
- Run
mix deps.auditandmix sobelowin 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
- CWE-338: Use of Cryptographically Weak PRNG, MITRE, fetched 18 October 2026
- Erlang rand module, Erlang/OTP docs, fetched 18 October 2026
- Erlang crypto module, Erlang/OTP docs, fetched 18 October 2026
Last reviewed: 18 October 2026.