OpenSSL Cheatsheet

Hashing and HMAC

Use this OpenSSL reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Hash a file or a string

dgst takes an algorithm flag and any number of files. Each digest algorithm is also its own subcommand shortcut.

openssl dgst -sha256 file.txt
openssl sha256 file.txt                  # same thing
openssl dgst -sha256 a.txt b.txt c.txt   # one line per file
echo -n "hello" | openssl dgst -sha256   # stdin, no filename in output
printf 'hello' | openssl sha256
$ openssl dgst -sha256 f.txt
SHA2-256(f.txt)= 5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e...

OpenSSL 3.x prints SHA2-256(...), not SHA256(...). Any script grepping for the old label breaks on upgrade. Use -r for the coreutils format instead, which is stable and diffable against sha256sum:

$ openssl dgst -sha256 -r f.txt
5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03 *f.txt

openssl dgst -sha256 -r f.txt | cut -d' ' -f1   # bare hex

Algorithms

openssl dgst -list prints every digest the build supports.

FlagOutput bitsUse
-sha256256The default choice for everything
-sha384, -sha512384, 512Longer digests, faster than SHA-256 on 64-bit
-sha512-256256SHA-512 truncated, avoids length extension
-sha3-256, -sha3-512256, 512Keccak family, different construction
-shake128, -shake256variableNeeds -xoflen N
-blake2b512, -blake2s256512, 256Fast, not in TLS
-sm3256Chinese national standard
-sha1160Collisions are practical, legacy verification only
-md5128Broken, checksums against old tools only
openssl dgst -shake256 -xoflen 64 file.txt
openssl dgst -list | tr -s ' ' '\n' | grep sha3

Do not hash passwords with any of these. A digest is not a password hash. Use Argon2id, scrypt, or bcrypt in your application, or openssl kdf with SCRYPT or PBKDF2 if you must stay inside OpenSSL.

HMAC

HMAC is a keyed digest, which is what you want for webhook signatures and API request signing.

openssl dgst -sha256 -hmac "secretkey" file.txt
echo -n "payload" | openssl dgst -sha256 -hmac "secretkey"
echo -n "payload" | openssl dgst -sha256 -hmac "secretkey" -binary | base64
$ openssl dgst -sha256 -hmac "secretkey" f.txt
HMAC-SHA2-256(f.txt)= e306479813b3b6fb2a859ffa8f465f69bae4deed367b91...

The mac subcommand is the 3.x way and takes a hex key, so it handles binary keys that -hmac cannot express. The MAC name comes last, after every option.

openssl mac -digest SHA256 -macopt key:secretkey -in f.txt HMAC
openssl mac -digest SHA256 -macopt hexkey:0011deadbeef -in f.txt HMAC
openssl mac -digest SHA256 -macopt key:k -in f.txt -binary HMAC | base64

-macopt accepts key: for ASCII and hexkey: for hex. Other MAC names: CMAC (with -cipher AES-256-CBC), KMAC128, KMAC256, GMAC, Poly1305.

Reproducing a webhook signature, the thing people actually come here for:

# Stripe / GitHub style: HMAC-SHA256 of the raw body, hex
printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -r | cut -d' ' -f1

# base64 flavour (AWS, Slack legacy)
printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary |
  openssl base64 -A

Sign and verify with a key

Same subcommand, but with a private key instead of an HMAC secret. This is a real digital signature, verifiable by anyone holding the public key.

# sign
openssl dgst -sha256 -sign key.pem -out file.sig file.txt

# verify with the public key
openssl pkey -in key.pem -pubout -out pub.pem
openssl dgst -sha256 -verify pub.pem -signature file.sig file.txt

# verify with the private key (it contains the public parts)
openssl dgst -sha256 -prverify key.pem -signature file.sig file.txt
Verified OK
Verification failure

Base64 armour the signature for anything text based, and use RSA-PSS rather than the default PKCS#1 v1.5 padding on new systems:

openssl dgst -sha256 -sign key.pem file.txt | openssl base64 -A > file.sig.b64

openssl dgst -sha256 -sign key.pem \
  -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 \
  -out file.sig file.txt

openssl dgst -sha256 -verify pub.pem \
  -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-1 \
  -signature file.sig file.txt

Ed25519 signs the whole message and refuses a pre-hash, so use pkeyutl:

openssl pkeyutl -sign -inkey ed.key -rawin -in file.txt -out file.sig
openssl pkeyutl -verify -pubin -inkey ed.pub -rawin -in file.txt \
  -sigfile file.sig

Gotchas

- echo adds a newline. echo "hello" | openssl sha256 hashes 6 bytes, not 5, and will never match the hash your language library produced. Use echo -n or printf '%s'. - -hmac puts the secret in your shell history and process list. Read it from a file or an environment variable in anything real. - HMAC comparison must be constant time. Comparing in a shell with = is fine for debugging, never for a request handler. - Verified OK goes to stdout, and the exit code is what you check. 0 for a valid signature, 1 otherwise. - The digest used to sign and the digest used to verify must match. -sha256 signing verified with -sha512 always fails, with a message that says nothing about the mismatch. - -sigopt must be repeated identically on both sides. A PSS signature checked without the PSS options reads as a plain failure.