21 lines
1.1 KiB
Bash
Executable File
21 lines
1.1 KiB
Bash
Executable File
#!/bin/bash
|
|
# Current TOTP code from a base32 seed (pure bash + openssl + coreutils).
|
|
# usage: totp.sh <base32-seed> [step-seconds] [digits]
|
|
set -euo pipefail
|
|
SECRET="${1:?usage: totp.sh <base32-seed> [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"
|