#!/bin/bash # Current TOTP code from a base32 seed (pure bash + openssl + coreutils). # usage: totp.sh [step-seconds] [digits] set -euo pipefail SECRET="${1:?usage: totp.sh [step] [digits]}" STEP="${2:-30}"; DIGITS="${3:-6}" NOW="${TOTP_NOW:-$(date +%s)}" # TOTP_NOW: RFC-6238 test vector override KEYHEX=$(printf '%s' "$SECRET" | tr -d ' =\n-' | base32 -d 2>/dev/null | od -An -tx1 | tr -d ' \n') [ -n "$KEYHEX" ] || { echo "bad base32 seed" >&2; exit 2; } COUNTER=$(printf '%016x' $(( NOW / STEP ))) # bash vars cannot hold null bytes - keep the counter as a FORMAT string and # let printf emit the raw bytes straight into the pipe. FMT=$(printf '%s' "$COUNTER" | sed 's/../\\x&/g') # intentional: FMT emits raw counter bytes (NULs) - the whole point # shellcheck disable=SC2059 MAC=$(printf "$FMT" | openssl dgst -sha1 -mac HMAC -macopt "hexkey:$KEYHEX" -binary \ | od -An -tx1 | tr -d ' \n') OFFSET=$(( 0x${MAC: -1} )) CODE=$(( (0x${MAC:$((OFFSET*2)):8} & 0x7fffffff) % (10 ** DIGITS) )) printf "%0${DIGITS}d\n" "$CODE"