TOC »
totp
Description
An implementation of HMAC-Based (HOTP, RFC 4226) and Time-Based (TOTP, RFC 6238) One-Time Password algorithms, plus RFC 4648 base32 encoding for secret provisioning and an otpauth:// "Key Uri Format" URI builder for QR-code enrollment in authenticator apps (Google Authenticator, Authy, and similar).
hotp-verify and totp-verify are side-effect-free procedures: they return the matched counter/step offset (or #f), and leave counter resynchronization (RFC 4226 section 7.4) and replay prevention (RFC 6238 section 5.2) to the caller, since both require stateful information per secret that this library has no opinion about how to store.
Author
Repository
Source repository: chicken-totp.
Requirements
hmac sha1 sha2 openssl datatype uri-common
Procedures
HOTP (RFC 4226)
- hotp-generate SECRET COUNTER #!key (digits 6) (algorithm 'sha1)procedure
Computes the HOTP value for the shared secret SECRET (a string of raw bytes) and the non-negative exact integer COUNTER, returning it as a decimal string zero-padded to digits characters. algorithm selects the HMAC hash function and may be 'sha1, 'sha256, or 'sha512.
- hotp-verify SECRET COUNTER CODE #!key (digits 6) (algorithm 'sha1) (look-ahead 0)procedure
Verifies CODE against COUNTER and, if look-ahead is greater than zero, against each of the look-ahead counters following it (RFC 4226 section 7.4 resynchronization). Returns the matched offset (an exact integer between 0 and look-ahead, inclusive) on success, or #f if no counter in that range produces CODE.
On a successful verification, the caller should persist (+ COUNTER offset 1) as the new stored counter for this secret, so that a code cannot be verified a second time.
TOTP (RFC 6238)
- (totp-generate SECRET #!key (time (current-seconds)) (period 30) (t0 0) (digits 6) (algorithm 'sha1)) -> stringprocedure
Computes the TOTP value for SECRET at the given Unix time (in seconds), using a time step of period seconds since epoch offset t0. Equivalent to calling hotp-generate with the counter (quotient (- time t0) period).
- (totp-verify SECRET CODE #!key (time (current-seconds)) (period 30) (t0 0) (digits 6) (algorithm 'sha1) (window 1)) -> fixnum or #fprocedure
Verifies CODE against the time step at time and, symmetrically, against the window steps immediately before and after it (RFC 6238 section 5.2's recommended clock-drift tolerance). Returns the matched step offset (an exact integer between (- window) and window, inclusive; 0 means an exact match at the current step) on success, or #f otherwise.
This procedure does not itself guard against replay: since a TOTP code remains valid for the whole period-second step it belongs to (and longer still under a nonzero window), an application must track, per secret, the absolute step of the last code it accepted and refuse any future match at or before that step. See totp-time-step below and the stateful-verify.scm example.
- (totp-time-step #!key (time (current-seconds)) (period 30) (t0 0)) -> fixnumprocedure
Returns the absolute time step for time, i.e. (quotient (- time t0) period). Adding totp-verify's returned offset to this value recovers the absolute step a successful verification matched, which is what a caller should compare against (and then store as) its "last accepted step" to prevent replay.
Secret generation and provisioning
- generate-totp-secret #!optional (n-bytes 20)procedure
Generates a fresh n-bytes-byte secret using OpenSSL's cryptographically secure random byte generator. The RFC 4226/6238 test vectors and most authenticator apps assume a 20-byte (160-bit) secret, which is the default.
- totp-key-uri SECRET LABEL #!key (issuer #f) (algorithm 'sha1) (digits 6) (period 30)procedure
Builds an otpauth://totp/... provisioning URI for SECRET and LABEL (typically an account name or email address), following the informal but universal "Key Uri Format" convention used by authenticator apps for QR-code-based enrollment. If issuer is given, it is included as the issuer query parameter. All percent-encoding of LABEL, issuer, and the query string is delegated to uri-common's make-uri/uri->string.
Base32 (RFC 4648)
- base32-encode STRINGprocedure
Encodes STRING (raw bytes) as RFC 4648 base32, using the standard A-Z2-7 alphabet and = padding. Used internally by totp-key-uri to encode the secret for the provisioning URI, and useful on its own for displaying or parsing a secret for manual entry.
- base32-decode STRINGprocedure
Decodes an RFC 4648 base32 STRING back to raw bytes. Invalid alphabet characters signal an error; trailing = padding is optional on input.
Utility
- constant-time-string=? A Bprocedure
Compares two strings for equality without leaking, via execution time, the position of the first differing character. hotp-verify and totp-verify use this internally to compare a submitted code against each candidate; exported since it is generically useful for comparing any other secret-derived string (e.g. backup/recovery codes).
Examples
(import totp) (define secret (generate-totp-secret)) (totp-generate secret) => "123456" (totp-verify secret (totp-generate secret)) => 0 ;; HOTP, with an explicit counter instead of a time ;; (using the RFC 4226 Appendix D test secret here, not the random ;; `secret` generated above) (define hotp-secret "12345678901234567890") (hotp-generate hotp-secret 0) => "755224" (hotp-verify hotp-secret 0 "755224") => 0 ;; A provisioning URI for QR-code enrollment (totp-key-uri secret "alice@example.com" issuer: "My App") => "otpauth://totp/alice%40example.com?secret=...&algorithm=SHA1&digits=6&period=30&issuer=My+App"
For a complete worked example of wrapping totp-verify with the per-secret replay-guard state RFC 6238 requires, see examples/stateful-verify.scm in the source repository.
Version History
- 1.0
- Initial release.
License
BSD 3-Clause.
Copyright (c) 2026, Ivan Raikov All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.