OneTimePass is a small HOTP and TOTP library with no runtime dependencies
One-time password library for HMAC-based (HOTP) and time-based (TOTP) passwords
At a glance
- What is it?
- onetimepass generates and validates HMAC-based and time-based one-time passwords in pure Python, with no runtime dependencies since version 2.0 and a constant-time token comparison. The API is small enough to read in a sitting, and the library is explicit about the parts, like replay protection, that it leaves to you.
- Who is it for?
- Adopt onetimepass if you need TOTP or HOTP in a Python service and want zero runtime dependencies plus a readable implementation you can audit. Skip it if you need a full second factor framework, since there is no provisioning, no recovery codes, no rate limiting and no QR code generation here, and skip it if you need replay protection without writing the bookkeeping yourself.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 124 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One module, two one-time password schemes
OneTimePass, whose import name is onetimepass, is a module for generating one-time passwords, namely HOTPs, HMAC-based one-time passwords, and TOTPs, time-based one-time passwords. The README notes they are used, for example, within the Google Authenticator application for Android or iPhone. That is the whole scope, and it is worth being clear about because the name suggests more. There is no user model, no enrolment flow, no device registry, no secret storage and no recovery. The library is the cryptographic core: given a shared secret, produce the number, and given a number and a secret, decide whether it matches. Everything around that, from how a user receives their secret to what happens when they lose their phone, is left to the application. For a service that already has accounts and enrolment, that is exactly the right shape. For a service that has none of that, you would be writing the hard half yourself before the library helps.
Version 2.0 removed the dependencies and added constant-time comparison
The changelog is the most informative document in the repository, because it shows how little has changed and how much has been modernised at once. Version 1.0.1 and 1.0.0 both date from 2015-07-31, and version 2.0.0 landed on 2026-06-21 after a decade of no release. What that release changed is worth listing. Python 2 support was dropped, so the library now requires Python 3.8 or newer. The six dependency was removed, and the package now has no runtime dependencies at all, which is confirmed in the pyproject.toml where dependencies is an empty list. Packaging switched to pyproject.toml with setuptools as the build backend. Type hints were added to the public API. Token comparison during validation became constant-time, which is the one change with a security consequence, since a naive string comparison of a token leaks timing information about how many leading characters matched. CI moved from Travis to GitHub Actions. The package classifiers now list Python 3.8 through 3.13 and mark the project as Production/Stable.
Secrets are case-insensitive and spaces are ignored
One behaviour deserves attention because it changes how you store and display secrets. The documentation notes that the secret is case-insensitive and that spaces are ignored, and the stated reason is readability. You can give your users a more readable representation of the secret, for example mfrg gzdf mztw q2lk instead of MFRGGZDFMZTWQ2LK, and pass it unchanged to the library. The same applies to the other functions that accept secrets. This is convenient for humans and a mild hazard for systems, because two different stored strings can map to the same secret, and any uniqueness constraint on a secrets table is now comparing strings that the library considers equivalent. The changelog shows this was a deliberate feature, added in 0.1.0 as skipping spaces if they are given in secret, and case folding arrived with 0.1.2 in 2013. If you are storing secrets, normalise them yourself before you store them, so that your database holds one canonical form.
Generating and validating tokens
The usage section is five steps, and the code is short enough to quote directly. The install is a single pip command.
$ sudo pip install onetimepassThe time-based token is one call.
import onetimepass as otp
my_secret = 'MFRGGZDFMZTWQ2LK'
my_token = otp.get_totp(my_secret)The HMAC-based token takes the interval number of the current trial, and the documentation explains why: if you are checking on the server, you have to check several values higher than the last successful one, determined for previous successful authentications.
import onetimepass as otp
my_secret = 'MFRGGZDFMZTWQ2LK'
my_token = otp.get_hotp(my_secret, intervals_no=3)Validation has the same shape for both schemes, and the time-based one takes the token and the secret.
import onetimepass as otp
my_secret = 'MFRGGZDFMZTWQ2LK'
my_token = 123456 # should be probably from some user's input
is_valid = otp.valid_totp(token=my_token, secret=my_secret)Replay protection is your job, and the API tells you so
The HOTP validation call is where the design shows, because the return value is not a boolean.
import onetimepass as otp
my_secret = 'MFRGGZDFMZTWQ2LK'
my_token = 123456 # should be probably from some user's input
last_used = 5 # store last valid interval somewhere else
is_valid = otp.valid_hotp(token=my_token, secret=my_secret, last=last_used)The last argument is the number of the last successfully checked interval number, and valid_totp skips it and starts checking from the next interval number. The is_valid variable is set to False if the token has not been identified as valid for the given secret and the checked interval range. If it has been successful, is_valid is assigned the number of the working interval number, which the documentation says should be saved into the database and supplied as the last argument next time the password is checked, so the same token cannot be used again. In other words the library hands you the replay bookkeeping as a return value and expects you to store it. That is the single most important integration detail in the whole API, and getting it wrong turns a one-time password into a reusable one for the length of the window.
What onetimepass deliberately leaves out
There are three things a second factor system normally has and this library does not. There is no enrolment and no secret distribution, so you must build the flow that hands a user their secret and stores it. There is no recovery, so a user who loses their authenticator needs a path you provide. And there is no rate limiting or lockout, so an attacker with a stolen secret can attempt tokens as fast as your login endpoint allows, which means the brute force protection has to live in your application. The library also predates the modern ecosystem conventions in a way that shows in the packaging, since the requirements.txt file is kept only for ease of use and backwards compatibility and points at a requirements directory, and the test extra is a single package, freezegun, which is the right tool for freezing time in token tests. The developers directory of the tree adds docs/, tests/ and a CONTRIBUTING.rst, and the documentation site lives at otp.readthedocs.io. The MIT licence is the permissive one, with the text in LICENSE.rst.
Editorial conclusion
Adopt onetimepass if you need TOTP or HOTP in a Python service and want zero runtime dependencies plus a readable implementation you can audit. Skip it if you need a full second factor framework, since there is no provisioning, no recovery codes, no rate limiting and no QR code generation here, and skip it if you need replay protection without writing the bookkeeping yourself. Before you ship, read the 2.0.0 changelog entry, because the last push was on 2026-06-21 and the constant-time comparison is new, and store the last used HOTP interval, since the library returns the working interval number and expects you to persist it.
Frequently asked questions
How do I install onetimepass and generate a TOTP token?
Install it with pip, or download and unpack it on your sys.path, or clone it as a Git submodule. Then call otp.get_totp(my_secret) with the shared secret to get the time-based token, and otp.get_hotp(my_secret, intervals_no=3) for the HMAC-based one.
How does onetimepass prevent a one-time password from being reused?
For HOTP, valid_hotp returns the working interval number on success, and the documentation says you should save it and pass it as the last argument on the next check, so the same token cannot be used again. The last argument is the number of the last successfully checked interval.
What changed in onetimepass 2.0.0?
Version 2.0.0 on 2026-06-21 dropped Python 2 support and now requires Python 3.8 or newer, removed the six dependency so the package has no runtime dependencies, switched packaging to pyproject.toml, added type hints to the public API, made token comparison during validation constant-time, and moved CI from Travis to GitHub Actions.
Are secrets in onetimepass case-insensitive?
Yes, the secret is case-insensitive and spaces are ignored, so you can show users a readable form such as mfrg gzdf mztw q2lk instead of MFRGGZDFMZTWQ2LK and pass it unchanged. The same applies to every function that accepts a secret.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tadeck-onetimepass)Was this useful?