Skip to main content

RFC 6238 TOTP and RFC 4226 HOTP — HMAC-SHA-1/256/512, zero dependencies

Project description

totp-strict

CI PyPI Python License: MIT

RFC 6238 TOTP and RFC 4226 HOTP — full HMAC-SHA-1/256/512 support, zero external dependencies.

Why not pyotp?

pyotp is the standard choice and works fine for HMAC-SHA-1. The gap shows up with HMAC-SHA-512.

RFC 6238 errata #2866 corrects a mistake in the original spec's test vectors: the secret length must match the HMAC block size for each algorithm. SHA-512 uses a 128-byte block, so the key should be 64 bytes — not the 20-byte key used for SHA-1. pyotp does not enforce this. The result is a silent mismatch against the official test vectors.

import base64, hashlib, pyotp
from totp_strict import TOTP

# RFC 6238 Appendix B test vector: T=59, HMAC-SHA-512, 8 digits
short_key = b"12345678901234567890"                                                # 20 bytes — pre-errata
long_key  = b"1234567890123456789012345678901234567890123456789012345678901234"    # 64 bytes — errata #2866

p = pyotp.TOTP(base64.b32encode(short_key).decode(), digits=8, digest=hashlib.sha512)
print(p.at(59))   # 69342147  ← does not match RFC 6238

t = TOTP(long_key, digits=8, algorithm="sha512")
print(t.at(59))   # 90693936  ← matches RFC 6238 ✓

totp-strict passes all 18 RFC 6238 test vectors across SHA-1, SHA-256, and SHA-512.

Feature pyotp totp-strict
HMAC-SHA-1
HMAC-SHA-256
HMAC-SHA-512 (errata-correct)
All RFC 4226 test vectors
All RFC 6238 test vectors (SHA-1/256/512) partial
QR provisioning URI (Key URI Format)
Zero external dependencies
Python 3.11+ typed

Installation

pip install totp-strict

Requires Python 3.11+. No external dependencies — only the standard library.

Usage

TOTP (time-based)

from totp_strict import TOTP

# Standard 6-digit TOTP (Google Authenticator compatible)
totp = TOTP(secret=b"your-secret-key", digits=6, algorithm="sha1")
print(totp.now())           # current code, e.g. "123456"
print(totp.at(1234567890))  # code at a specific Unix timestamp

# Verify with ±1-window drift tolerance (default)
totp.verify("123456")

# Strict — accept only the current window
totp.verify("123456", window=0)

# 8-digit TOTP with HMAC-SHA-256
totp = TOTP(secret=b"your-32-byte-key-here-xxxxxxxxxxx", digits=8, algorithm="sha256")
print(totp.now())

# Generate a QR code URI for Google Authenticator
uri = totp.provisioning_uri("alice@example.com", issuer="MyApp")
# → otpauth://totp/MyApp:alice@example.com?secret=...&algorithm=SHA1&digits=6&period=30

HOTP (counter-based)

from totp_strict import HOTP

hotp = HOTP(secret=b"12345678901234567890", digits=6)
print(hotp.at(0))                    # "755224"  (RFC 4226 test vector)
print(hotp.at(1))                    # "287082"
hotp.verify("755224", counter=0)     # True

API reference

TOTP(secret, digits=6, algorithm="sha1", interval=30, t0=0)

Parameter Type Default Description
secret bytes Raw secret bytes (not base32-encoded)
digits int 6 OTP length, 1–10
algorithm "sha1" | "sha256" | "sha512" "sha1" HMAC hash function
interval int 30 Time-step in seconds
t0 int 0 Unix epoch start (T0)
Method Returns Description
.now() str Current TOTP code
.at(timestamp) str TOTP code at a Unix timestamp
.verify(code, timestamp=None, window=1) bool Constant-time verification with drift tolerance
.provisioning_uri(name, issuer=None) str otpauth:// URI — see below

window=1 (default) accepts codes from the previous, current, and next interval (±30 s with the default step).

Provisioning URI

.provisioning_uri() produces an otpauth://totp/ URI that follows the Google Authenticator Key URI Format:

  • Label format: issuer:account — the colon and @ are left unencoded, all other special characters are percent-encoded
  • Secret: base32-encoded, padding stripped (no =)
  • Algorithm: uppercase (SHA1, SHA256, SHA512)

Scannable by Google Authenticator, Authy, and any app that supports the Key URI Format.

HOTP(secret, digits=6, algorithm="sha1")

Method Returns Description
.at(counter) str HOTP code at a counter value
.verify(code, counter) bool Constant-time verification

RFC compliance

RFC 4226 — HOTP test vectors

Secret: b"12345678901234567890", HMAC-SHA-1, 6 digits (Appendix D)

Counter Expected Status
0 755224
1 287082
2 359152
3 969429
4 338314
5 254676
6 287922
7 162583
8 399871
9 520489

RFC 6238 — TOTP test vectors

8-digit TOTP, interval=30, T0=0 (Appendix B + errata #2866)

Unix time SHA-1 SHA-256 SHA-512
59 94287082 ✓ 46119246 ✓ 90693936 ✓
1111111109 07081804 ✓ 68084774 ✓ 25091201 ✓
1111111111 14050471 ✓ 67062674 ✓ 99943326 ✓
1234567890 89005924 ✓ 91819424 ✓ 93441116 ✓
2000000000 69279037 ✓ 90698825 ✓ 38618901 ✓
20000000000 65353130 ✓ 77737706 ✓ 47863826 ✓

Run pytest tests/ -v to verify locally.

Development

git clone https://github.com/Suhas-123-cell/totp-strict
cd totp-strict
pip install -e ".[dev]"
pytest tests/ -v

Contributing

Bug reports and pull requests are welcome on GitHub.

When filing a bug report, include the secret, algorithm, and timestamp (or counter) that produces the wrong output, plus the expected value and a reference (RFC section or errata ID).

When opening a pull request, add a test case that covers the change and make sure all existing tests pass.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

totp_strict-0.1.0.tar.gz (6.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

totp_strict-0.1.0-py3-none-any.whl (6.6 kB view details)

Uploaded Python 3

File details

Details for the file totp_strict-0.1.0.tar.gz.

File metadata

  • Download URL: totp_strict-0.1.0.tar.gz
  • Upload date:
  • Size: 6.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for totp_strict-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5eb3744540c722bae5208fcc91ae7f2e10a6e39bbe1a03c529ae5c4e43f2a543
MD5 6f9a8e66579d0d4c776a572792ec17e1
BLAKE2b-256 dc683a7e85265471b62ad72a0369ae33b5d5a57c137e16530bf8d8b1fd62a9bd

See more details on using hashes here.

File details

Details for the file totp_strict-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: totp_strict-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 6.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.4

File hashes

Hashes for totp_strict-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2e18502bfb422a26947158aa81c2e5a4acfe1abc07e1a0cc0c2c612f03448b37
MD5 02a60a055b01e992f7d6b6646aee7a2b
BLAKE2b-256 d09338a289af3e57d2116ac4e4eacd8c040c764dca7f4839bb64e77d4a5ea15d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page