In Ruby, use the SecureRandom module for anything an attacker must not guess: session tokens, password reset links, API keys, salts and nonces. It draws from the operating system or OpenSSL. The plain rand method and Random class are built for simulations, and their output can be predicted.
What is SecureRandom backed by?
The Ruby documentation for SecureRandom lists three secure generators it can use: openssl, /dev/urandom and Win32. If none is available, it raises NotImplementedError instead of quietly falling back to something weaker. That failure is a feature. A crash at boot is better than tokens made from a guessable source.
The module is extended by Random::Formatter, so the formatting helpers work as class methods. The documented list includes alphanumeric, base64, choose, hex, rand, random_bytes, random_number, urlsafe_base64 and uuid.
require 'securerandom'
SecureRandom.hex(16)
That gives 16 random bytes, shown as 32 hex characters.
Which method fits which job?
Match the method to where the value will travel.
- A value that goes in a URL, such as a reset link:
urlsafe_base64. It avoids characters that need escaping. - A value that goes in a cookie or header:
hexorurlsafe_base64. - Raw key material for a cipher or HMAC:
random_bytes, with the length the algorithm asks for. - A short code a human types:
alphanumericwith a length you have calculated, plus rate limiting on the guess endpoint. - A random integer in a range, for example picking an index:
random_number(n).
Pay attention to length. Entropy comes from the number of random bytes, not the number of characters you see. Sixteen bytes is a common floor for tokens that are checked against a server side record. A longer code costs little.
What goes wrong in practice?
Here is a constructed example of a review finding. A Rails app builds a password reset token like this: it calls rand(36**8).to_s(36). That gives an eight character string from a general purpose generator. The state of that generator can in principle be recovered from enough observed outputs, and eight base 36 characters is a small search space even if it cannot. An attacker who triggers resets for a victim's email and can guess the token takes over the account.
The fix is one line: replace it with SecureRandom.urlsafe_base64(32), store a digest of the token rather than the token itself, and expire it quickly.
Other common mistakes:
- Seeding
Random.new(seed)with a timestamp or user ID, then using it for secrets. - Using
uuidas a secret. A UUID is an identifier. Treat a random version 4 UUID as unguessable only if you have confirmed how your library generates it, and prefer an explicit token for secrets. - Comparing tokens with
==. Use a constant time comparison such asActiveSupport::SecurityUtils.secure_compareorOpenSSL.fixed_length_secure_comparewhere available. - Generating a secret once in a script and committing it. Randomness does not help if the value lives in the repository.
What about cryptography beyond random numbers?
Random bytes are the input. The next decisions matter as much. Do not write your own cipher mode. Use the OpenSSL library that ships with Ruby with an authenticated mode such as AES-GCM, give each message a fresh random nonce, and never reuse a nonce with the same key. For passwords, use a slow password hashing function, not a fast digest. Check the documentation of the library you choose for the exact calls.
What to do
- Search the codebase for
rand(,Random.new,Random.randandsrandnear words like token, key, secret, nonce and salt. - Replace each hit with the
SecureRandommethod that fits the destination. - Choose lengths in bytes, and write the byte count in a constant with a comment.
- Store only digests of reset and invite tokens, and expire them.
- Compare secrets in constant time.
- Add a lint rule or review checklist item so new code does not reintroduce
randfor secrets.
Safeguard's SAST scan can flag weak randomness patterns in source, which helps with step 1 at scale. The SAST code scan demo shows the flow.
Quick check
Is Kernel rand fine for a session ID?
No. It is meant for general purpose randomness, and its output can be predicted. Use SecureRandom.
What happens if no secure generator is available to SecureRandom?
The documentation says it raises NotImplementedError rather than falling back to a weaker source.
Sources
Last reviewed: 18 October 2026.