Generate a random string in Bash

openssl rand -hex 32

openssl rand -hex 32 prints 64 hex characters made from 32 random bytes: 256 bits, more than any token needs. For 128 bits, the usual size of a session ID or an API key, use openssl rand -hex 16. OpenSSL 3 on Linux and the LibreSSL that macOS ships take the same flags.

openssl rand -hex 32
# 6963b23aa6d4a843c050c7e1e3ca7425ceffb7546e559fc3e7664a969c4047dc
openssl rand -hex 16
# 68ba35ff6044bda94bf0217feda2c5c3

The tool below shows the count for the first: 64 characters × log2(16) = 256 bits. OWASP’s Session Management Cheat Sheet sets 64 bits of entropy as the floor for a session ID.

Base64

openssl rand -base64 32 gives 44 characters for the same 32 bytes, with +, / and a trailing =. From 49 bytes up it breaks the output into lines of 64 characters. For a URL or a file name, swap the two symbols and drop the padding:

openssl rand -base64 32 | tr '+/' '-_' | tr -d '='
# RM8Nw_SvEyc7TJj0UCuejqtPkGuiTgvkv18rJ_nyNrQ

Straight from /dev/urandom

Without OpenSSL, read the kernel’s random device:

head -c 32 /dev/urandom | base64
# KRpkEAPsB6RP1qKmFvzah6tsORZUmQPrNRACmek0Uw4=

GNU base64 on Linux wraps at 76 characters, which starts at 58 bytes, so add -w 0 for longer tokens. macOS’s base64 doesn’t wrap.

For letters and digits only, let tr delete every other byte:

LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32; echo
# jywLOMQZOkVxorVl3A57K8fanBL41Lg7

Each byte tr keeps is one of the 62 characters, all equally likely, so there’s no bias; the tool counts 32 of them as 32 characters × log2(62) = 191 bits. head -c prints no newline, hence the echo.

On macOS, LC_ALL=C is required. In a UTF-8 locale, macOS’s tr reads the bytes as text and stops at the first invalid sequence:

tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32
# tr: Illegal byte sequence

It may print a few characters first, and the pipeline still exits 0, so a script gets a short token and no error. GNU tr works on bytes, so Linux doesn’t need the prefix.

Under set -o pipefail the same line fails differently: head exits after 32 bytes, tr is killed by SIGPIPE, and the pipeline’s status is 141. With set -euo pipefail, token=$(…) ends the script there without a message. openssl rand has neither problem.

Not $RANDOM

$RANDOM is a number from 0 to 32767, 15 bits from a seeded generator, and gluing several together doesn’t make it secret. Bash 5.1 added $SRANDOM, 32 bits from the system’s random source, but /bin/bash on macOS is still 3.2, where it expands to nothing.

Try it in the Random String Generator

Drawing bytes from your browser’s random source…

Runs in your browser. Nothing you enter is sent anywhere.