Hacker News new | past | comments | ask | show | jobs | submit login
Tiny Toy TOTP Generator in 18 Lines of Python (github.com)
142 points by susam 64 days ago | hide | past | web | favorite | 44 comments



In practice, clocks are often out of sync enough that you want to give some leeway and accept some codes on either side of the current code. The implementation I use for sourcehut is similarly tiny:

https://git.sr.ht/~sircmpwn/meta.sr.ht/tree/master/metasrht/...


Yes, in fact the RFCs for both HOTP and TOTP have a section each dedicated to resynchronization of counter/clock to account for client and server being out of sync. Here are the relevant sections:

- RFC 4226 (HOTP): Section 7.4 (Resynchronization of the Counter): https://tools.ietf.org/html/rfc4226#section-7.4

- RFC 6238 (TOTP): Section 6 (Resynchronization): https://tools.ietf.org/html/rfc6238#section-6

In case of HOTP, the client's counter could be ahead of the server's counter if a user requests multiple HOTPs from the client before the user presents the HOTP to the server. Therefore the server should look ahead according to a permissible look-ahead window to see if any succeeding HOTP value matches the HOTP presented by the user or client.

In case of TOTP, there is of course the problem of clock drifts. Therefore the server should look backward as well as forward by a few time steps to see if any TOTP backward or forward matches the TOTP presented by the user or client.


In the past, when I've implemented TOTP on the server, I've allowed for a couple of time periods of drift. But I recorded the time period that last matched, and only allowed subsequent authentication attempts to be strictly greater than the last successful time period, to prevent replay attacks.


Yes, RFC 6238 requires this too. Quoting from https://tools.ietf.org/html/rfc6238#section-5.2:

> Note that a prover may send the same OTP inside a given time-step window multiple times to a verifier. The verifier MUST NOT accept the second attempt of the OTP after the successful validation has been issued for the first OTP, which ensures one-time only use of an OTP.


Plugging my hotp/totp python library hotpie[0]. Written originally in 2010. Around 100 lines including comments and tests against the RFC. Available on Pypi for python 2.x and 3.x.

The beauty of HOTP/TOTP for me is its simplicity. And being able to write a simple implementation makes it easy to test, debug and be sure that there are little or no holes.

[0] https://github.com/gingerlime/hotpie


I remember having fun implementing that rfc and having a sort of Google authenticator on my Linux box besides Android.

https://github.com/bjornua/totp.py/blob/master/totp.py


Being of a certain age and in the UK, I was disappointed to find this is not an 18 line Top Of The Pops generator... :)


Comes with mandatory "whole lotta love" algorithm and bad lip sync


Same here!


No nonces, either.


5 most important lines:

  import base64
  import hmac
  import struct
  import sys
  import time


I'd say these are the 4 most important, because they show the actual core of the algorithm; everything else is just input/output:

    mac = hmac.new(secret_bytes, counter_bytes, digest).digest()
    offset = mac[-1] & 0x0f
    truncated = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff
    return str(truncated)[-digits:].rjust(digits, '0')
In other words, HMAC your secret key with the time and pick digits using the output of the HMAC.


I get your point but equally the point of programming languages is to abstract away the harder stuff. The question is just how much abstraction developers want between the code and the execution.

I mean, you could make the same claim about one written in C because it doesn’t include stdio.h. So where do you draw the line? Assembly?


My point is that saying that something is written in 18 lines of code when actually all the heavy lifting is done by the libraries is misleading. That doesn’t mean I don’t appreciate Python’s expressiveness and rich standard library.


Yeah, but that comment can be made about literally every software in existence. Even core language keywords are doing a lot of heavy lifting in the interpreter.

In Python, the stdlib is effectively part of the language, so as long as they're not installing third-party libs, I don't see how it can be misleading.


I think you both raise valid points.

This was an interesting thing to look at to see get a quick idea of how TOTP works.

On the flip side, though, if this we had this hypothetical implementation:

    import totp

    if __name__ == '__main__':
        totp.generate()
Well, we probably wouldn't be here discussing it.


Well, the key part essentially is just like that, it's the single line

     mac = hmac.new(secret_bytes, counter_bytes, digest).digest()
and everything else is just converting data to/from the proper format for this function.


Well sure, but given that TOTP is itself just formatted HMAC, that "everything else" of formatting is the essence of TOTP.

Otherwise you probably wouldn't say you're implementing TOTP, you'd say you're implementing HMAC.


Single most important line: TOTP stands for Time-based One-Time Password


Along with micropython-hmac (MicroPython doesn't include hmac), and the touch sensor on an ESP32 I threw together an authenticator with the same inspiration as this project. It seems to work quite well, overall.


I haven't used MicroPython in a long time, but it seems to include hmac: https://github.com/micropython/micropython-lib/tree/master/h... right?


micropython-lib packages aren't included in the base MicroPython (well, not the main one. The Pycopy fork does), and are published on PyPI under micropython-x. You've found the source for the package I named.


Ah, didn't know that, TIL!



The RFCs for HOTP[0] and TOTP[1] are very readable.

I built a small cli utility to manage my TOTP logins easily using them as reference.

[0] https://tools.ietf.org/html/rfc4226

[1] https://tools.ietf.org/html/rfc6238


Am I the only one who found the use of acronyms here a little annoying? Like, use the whole word expansion at least once before introducing the acronym. That should be standard practice regardless of discipline and audience.


Which acronym? Expanding "TOTP" isn't going to help anyone; what you really want is the short sentence that simply explains what TOTP is.


I didn't know what it meant, and looked it up. "Time-based One Time Password" explains a hell of a lot more that "TOTP." From just those 4 words I figured out we were talking about the kind of thing that powers 2-factor auth implementations where get a time-based one-time code.

Before I looked it up, I thought it was somehow related to NTP.


My point is that "time based one-time password" doesn't really tell you anything about what TOTP is; what you want is the phrase "the protocol code-based 2FA applications like Google Authenticator use", after which you don't care anymore about the stupid name the protocol has.

It's a little bit like expanding "transport control protocol". I mean, sure, I guess it's marginally better than the acronym TCP? But really, that's not the clarification you want to provide to someone who doesn't know what TCP is.

Also, obviously, if you don't know what TOTP is, you might just not be the audience for the article. Remember that most people don't write their personal blogs (or, in this case, their Github personal projects) specifically for an audience of Hacker News people, even though HN sometimes makes it seem that way.


Although not at the very first line, I do expand the acronyms in the first section:

> Introduction: TOTP stands for Time-based One-Time Password. At the heart of the TOTP algorithm lies the HOTP algorithm. HOTP stands for HMAC-based One-Time Password.


An acronym within an acronym, too.


>However, doing so defeats the purpose of two-factor authentication (2FA).

There are still some 2FA benefits from this. The biggest one is for people who reuse passwords. Some websites might put you into a higher security tier if you have 2FA. Also if you have to choose between SMS vs this, this has some benefits, for example it can't be hijacked by social engineering your phone provider.

Also it says regular TOTP protects you from keyloggers. That's not fully true, because the keylogger could steal your TOTP code as you enter it into the website. If the keylogger is realtime, it could log into your account before you're able to. Or if you assume there's malware on your computer, it could steal your cookies, or perform whatever account actions it wants directly on your computer.


The README does not say that TOTP protects us from keyloggers. I was either not clear in writing that paragraph or you may have misundertood that paragraph. Here's what the README says,

> If your desktop/laptop device is compromised, then both authentication factors would be compromised. The attacker can steal the first authentication factor that only you should know (e.g., password) by running a key logger on the compromised device. The attacker can also steal the second authentication factor that only you should have ...

What I mean here is that generating the TOTP on the same system that we would use to log into a website with 2FA defeats the purpose of 2FA because the attacker can steal the TOTP secret key in addition to stealing the password (keylogger being one way to steal the password).


I think the grandparent is just trying to say that while it defeats one of the purposes behind 2fa, it doesn't defeat all of them. Security is all about trade-offs and defense in depth and 2fa is still valuable in this sense even with the 2fa secret stored on your laptop. That is, it trades off some security for convenience but is still more secure than not having 2fa.


I agree. I think it is acceptable to trade some security for convenience and in this case the convenient solution is still more secure than not having 2FA at all. However, the fact that some security is being traded for convenience should be documented in the README, otherwise one can criticize that the README is promoting a less secure usage of TOTP.


>because the attacker can steal the TOTP secret key

My question is why are we worrying so much about theft of the secret key? There are easier things to steal and abuse (cookies, TOTP codes, website data). Those can be stolen even if the TOTP generator is a different device than the logging in device.


I agree that there are easier things to steal. However, that should not allow us to be lax with the security of the TOTP secret key. I think it still makes sense to worry about the theft of the TOTP secret itself. It's just an additional thing to protect along with cookies, TOTP codes, website data, etc.


If we don't have to follow PEP8, how many lines can we condense it to? I am going to try.


Readability is more important than your ego.


MFA - what you have.

I personally prefer this golang library to generate my OTP codes https://github.com/pquerna/otp as it's much faster than running a python script.

My personal computer is the 'what I have' to do the second factor of authentication for most sites via a shell script, followed by xclip (linux) or pbcopy (mac). I prefer this over browser extensions using javascript to paste in your OTP code for you.


If you want raw speed, oathtool is hard to beat. Only 132KB to load to memory, compared to the megabytes of the typical Go binary. But unless you're playing an FPS where you have to input TOTP codes to shoot, this script is probably fast enough (80ms on my old laptop).


Only 132KB to load to memory

It's amusing to see people think 100KB+ is small, when the whole TOTP algorithm, including SHA1 (probably the biggest part), and the input/output conversion, likely needs only a few KB of code. With the exception of SHA1, everything else is doable in dozens of bytes.


True, but I'm just talking about the sizes of the binaries generated by the default settings of common compilers (gcc, in the case of oathtool), for comparing orders of magnitude. I'm sure there's plenty of fat you could trim by simply tuning the compilation a bit.

(Oathtool also supports SHA2, since it may be used by other implementations, per the RFC. )


The python binary itself will take some memory. Although maybe it can be shared with other running python processes.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: