Wiki
Download
Manual
Eggs
API
Tests
Bugs
show
edit
You can edit this page using
wiki syntax
for markup.
Article contents:
[[tags: egg]] [[toc:]] == totp === Description An implementation of HMAC-Based ({{HOTP}}, [[https://www.ietf.org/rfc/rfc4226.txt|RFC 4226]]) and Time-Based ({{TOTP}}, [[https://www.ietf.org/rfc/rfc6238.txt|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 [[/users/ivan-raikov|Ivan Raikov]] === Repository Source repository: [[https://github.com/iraikov/chicken-totp|chicken-totp]]. === Requirements [[hmac]] [[sha1]] [[sha2]] [[openssl]] [[datatype]] [[uri-common]] === Procedures ==== HOTP (RFC 4226) <procedure>(hotp-generate SECRET COUNTER #!key (digits 6) (algorithm 'sha1)) -> string</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}}. <procedure>(hotp-verify SECRET COUNTER CODE #!key (digits 6) (algorithm 'sha1) (look-ahead 0)) -> fixnum or #f</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) <procedure>(totp-generate SECRET #!key (time (current-seconds)) (period 30) (t0 0) (digits 6) (algorithm 'sha1)) -> string</procedure> 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)}}. <procedure>(totp-verify SECRET CODE #!key (time (current-seconds)) (period 30) (t0 0) (digits 6) (algorithm 'sha1) (window 1)) -> fixnum or #f</procedure> 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. <procedure>(totp-time-step #!key (time (current-seconds)) (period 30) (t0 0)) -> fixnum</procedure> 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 <procedure>(generate-totp-secret #!optional (n-bytes 20)) -> string</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. <procedure>(totp-key-uri SECRET LABEL #!key (issuer #f) (algorithm 'sha1) (digits 6) (period 30)) -> string</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) <procedure>(base32-encode STRING) -> string</procedure> 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. <procedure>(base32-decode STRING) -> string</procedure> Decodes an RFC 4648 base32 {{STRING}} back to raw bytes. Invalid alphabet characters signal an error; trailing {{=}} padding is optional on input. ==== Utility <procedure>(constant-time-string=? A B) -> boolean</procedure> 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 <enscript highlight="scheme"> (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" </enscript> 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.
Description of your changes:
I would like to authenticate
Authentication
Username:
Password:
Spam control
What do you get when you add 2 to 24?