period setting does not seem to work (stuck at default) and is actually twice as long as defined

Issue #94 new
Stefan Schelm created an issue

While trying out different implementations for a TOTP application I stumbled over a weird behaviour: The timing behaviour for the passlib library did not work as expected. I wrote a short script to directly compare passlib and pyotp. As you can see in the output below, the token was actually the same (good) and the timing verification was the same for both implementations (5s). When stepping through the time window in one second intervals, the result was quite different though: 1) The pass lib result indicates that the 5s period was not used, rather the 30s default seems to have been used 2) The passlib implementation uses the period symmetrically around the time reference, in effect doubling the allowed time window

I verified the results with 6 and 8 digits and ran it also on a Linux system with the same results.

(script result)-------------- version data platform info: Darwin-16.6.0-x86_64-i386-64bit python info: 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13) [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] passlib version: 1.7.1 pyotp version: 2.2.6

parameters Allowed Duration (s): 5 current datetime: 2017-07-20 14:55:51.484219 current datetime in s: 1500555351 key_string: "this is a test" encoded key_string: b'ORUGS4ZANFZSAYJAORSXG5A='

passlib setup full token: <TotpToken token='977416' expire_time=1500555355> passlib token-base: <passlib.totp.TOTP object at 0x103592320> verify passlib token-base duration: 5 passlib token: 977416

pyotp setup pyotp token-base: <pyotp.totp.TOTP object at 0x10358acc0> verify pyotp token-base duration: 5 pyotp token: 977416

passlib switched to true at (unix epoch, offset): 1500555320,-31 pyotp switched to true at (unix epoch, offset): 1500555350,-1 pyotp switched to true at (unix epoch, offset): 1500555355,4 passlib switched to false after true at (unix epoch, offset): 1500555385,34

Comments (1)

  1. Eli Collins repo owner

    Thanks for reporting that (and for testing things so closely!)

    After examining the issue, I'm not sure whether this is something I should correct in passlib's defaults, clarify in the documentation, or a mixture of both:

    The 5s period is still being used (otherwise the tokens wouldn't match at all, since they depend on the time + period).

    Instead, what's causing the difference in behavior is that passlib's TOTP().match() searches across a time range for matching tokens, to account for the effects of user transcription & network delay, as well as unpredictable network & clock difference jitter. The match function uses two parameters to control this: window and skew: it then searches for any matching tokens within [curtime + skew - window ... curtime + skew + window]. As of 1.7.1, window defaults to 30s, which is where the behavior you're seeing comes from.

    That default window might be too generous. I'm currently testing out (window=15, skew=-5) in one of my production applications, and it hasn't (yet) introduced too many false negatives for users. Unfortunately I don't have much data yet, and don't feel confident about how small I can make the defaults -- hence 30s was chosen as generous enough to swallow most jitter, while small enough to not cause problems with the recommended time period value (also 30s).

    In any case, pyotp defaults to valid_window=0, for no jitter correction; so changing to use token_base_passlib.match(token_passlib, time=check_time, window=0) should make the behaviors match. Be aware that pyotp's valid_window is measured in integer period units, rather than absolute seconds, so the value you need to use for the two libraries will differ in non-zero cases.

  2. Log in to comment