pyauth / pyotp Goto Github PK
View Code? Open in Web Editor NEWThis project forked from mdp/rotp
Python One-Time Password Library
Home Page: https://pyauth.github.io/pyotp/
License: Other
This project forked from mdp/rotp
Python One-Time Password Library
Home Page: https://pyauth.github.io/pyotp/
License: Other
random_base32
calls random.choice
which uses Python random()
which is implemented using the Mersenne Twister algorithm. This RNG is not cryptographically secure, and given enough of the output is is possible to recover the seed (for example https://github.com/fx5/not_random).
A scenario where this might occur is if random_base32
is used by a service provider. A user requests a new OTP key a few thousand times in a row, recovers the PRNG seed, and then is able to derive all other OTP keys generated by the same server process.
This is easy enough to fix, just use os.urandom
if available.
After doing some testing with the iOS apps OTP Auth
and LastPass Authenticator
I've come to notice these (and likely more) interpret the issuer string Hello+World
to be Hello+World
when they should be rendering as Hello World
. pyotp currently encodes with the urllib.quote()
method which uses the plus sign to encode spaces. When I changed the issuer string from Hello+World
to Hello%20World
all tested apps correctly understood this to be a space.
It should be noticed that the plus sign is only interpreted as a space in application/x-www-form-encoded
content, while the true HTML encoding (as referenced by current TOTP standards) to be used with the issuer name uses %20
as a space character.
Let me know if you have any questions regarding the matter.
Commit 6cedd88 changed utils.build_uri
to use string formatting. If the new format string were changed slightly, this package would be compatible with Python 2.6 again.
Replace:
base_uri = 'otpauth://{}/{}?{}'
with:
base_uri = 'otpauth://{0}/{1}?{2}'
Provide primitives for FIDO U2F RP operation.
How can I get expiration date for TOTP? totp.now()
without this information is a bit wired...
I've noticed that upon generating a new TOTP with provisioning_uri (for Google) the string occasionally switches the secret & issuer causing the QR generation to break (with qrious).
Working with qrious:
otpauth://totp/Company:name%domain.com?secret=2345ABCD6789EFGH&issuer=Company
Not Working with qrious:
otpauth://totp/Company:name%domain.com?issuer=Company&secret=2345ABCD6789EFGH
Should this be shared with the people working on qrious, is the format not important?
pip freeze
gets me pyotp==2.0.1
, but pyotp.VERSION
is still at 1.4.2
. Which one is correct? :)
According to Section 5.2 of RFC6238,
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.
However, the existing implementation (v2.2.6) appears to violate this:
$ python
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyotp; key = pyotp.random_base32(length=20)
>>> t = pyotp.TOTP(key)
>>> t.now()
'925243'
>>> t.verify('925243')
True
>>> t.verify('925243')
True
>>> t.verify('925243')
True
>>> t.verify('925243')
True
>>>
This could be a security issue: an attacker could conceivably MITM the connection between the verifier and provider, get the credentials & TOTP value, and use those to log in within the 30-second window. This would appear to defeat the verification because the TOTP can be replayed.
Alternatively, an attacker could shoulder-surf the victim and then log in separately within 30 seconds.
at https://github.com/pyotp/pyotp/blob/8e490934a6d9ef27fdbbb7ec94c7ee9cf83cba9d/src/pyotp/utils.py#L69.
hmac.compare_digest
does not support strings which contain non-ASCII characters.
User typed codes would typically be ASCII, but sometimes with non-ASCII. In particular, Japanese people often use FULLWIDTH digits (\uff10
[0] - \uff19
[9]) instead of ASCII digits.
So, to prevent above exception, pyotp should normalize codes based on Unicode before pass to hmac.compare_digest
or simply ignore TypeError
.
The last release available via pip is v2.0.1 and it is from Sep 28, 2015. It would be nice if you create a new release v2.0.2 with all the new features since then.
I could easily download the code from git hub of course, but I would like to use library in a Docker container and not having the latest version the the pip repo is kind of pain.
I use the latest google authenticator in app store. I scan the example qr code, and add it to google authenticator. But with the code:
import pyotp
totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
print("Current OTP:", totp.now())
I get :
('Current OTP:', 94511)
meanwhile in the google authenticaor is 456629.
when I use a golang libary opt, that works correctly.
Is this a bug?
Currently, if we were to run the library with Python3.6, we will have this error:
File "C:\Python36\lib\site-packages\pyotp\otp.py", line 49, in byte_secret
self.secret += '=' * (8 - missing_padding)
TypeError: can't concat str to bytes
By simple change of the code in otp.py, will make it work:
def byte_secret(self):
missing_padding = len(self.secret) % 8
if missing_padding != 0:
self.secret += b'=' * (8 - missing_padding)
return base64.b32decode(self.secret, casefold=True)
Because of overriding __init__() with *args, **kwargs argument we are missing arguments documentation for base class.
Check out the itsdangerous timing resistant compare function, which does not have the equality short-circuit:
def constant_time_compare(val1, val2):
"""Returns True if the two strings are equal, False otherwise.
The time taken is independent of the number of characters that match. Do
not use this function for anything else than comparision with known
length targets.
This is should be implemented in C in order to get it completely right.
"""
if _builtin_constant_time_compare is not None:
return _builtin_constant_time_compare(val1, val2)
len_eq = len(val1) == len(val2)
if len_eq:
result = 0
left = val1
else:
result = 1
left = val2
for x, y in izip(bytearray(left), bytearray(val2)):
result |= x ^ y
return result == 0
Not a bug. Feature request.
How do we get the Time remaining for totp.now()
Quoting usage documentation:
totp = pyotp.TOTP('base32secret3232')
totp.now() # => '492039'
# OTP verified for current time
totp.verify('492039') # => True
time.sleep(30)
totp.verify('492039') # => False
This implies that password is valid for 30 seconds after its generation which is not true. When verifying the password this way, it's valid only until the time interval for which it was generated ends.
I suggest to either explicitly state this or change the example to include valid_window
option.
I just tried the sample code but it doesn't work. I scanned the QR-Code into Google Authenticator and ran
import pyotp
totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
print("Current OTP:", totp.now())
but the codes do not match.
As of now it is not possible to hand a timezone aware datetime to TOTP. To be even more specific (because timezones are always a subtle matter...), it is not possible to hand a datetime expressed in an other timezone than the local timezone to pyotp.
The root of this issue is in pyotp.TOTP.timecode()
, the mktime
used there transforms a time tuple to the number of the seconds since "the EPOCH in the local timezone" (which is a weird concept).
Would you be willing to accept a PR fixing this issue?
For reference that's how we solved this problem on our side, and how it could be ported to pyotp with a minimal change footprint:
class TOTP(pyotp.TOTP):
def timecode(self, for_time):
if for_time.tzinfo:
i = calendar.timegm(for_time.utctimetuple())
else:
i = time.mktime(for_time.timetuple())
return int(i / self.interval)
import pyotp
t = pyotp.TOTP('secret')
t.provisioning_uri(u'кирилица')
the exception is:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/damjan/.local/lib/python2.7/site-packages/pyotp/totp.py", line 54, in provisioning_uri
return utils.build_uri(self.secret, name, issuer_name=issuer_name)
File "/home/damjan/.local/lib/python2.7/site-packages/pyotp/utils.py", line 40, in build_uri
'name': quote(name, safe='@'),
File "/usr/lib/python2.7/urllib.py", line 1303, in quote
return ''.join(map(quoter, s))
KeyError: u'\u043a'
the same code works in python3 and its str
type (which is same as unicode) and seems to url-escape the string.
I'm not sure what the standards say about it though?
I am developing a authenticator app in python, like Authy. Something similiar to a CLI script that I run and request my permission each time that I attempt to access to my account . I know that there is an Authy module for python but can I use pyotp to grant access?
I would love to have pyotp as a command line tool, being able to get OTP without having to write a (small) Python script and make it executable.
My proposal is to include a script that reads arguments and talks to the pyotp library, later including it in the PATH of python commands.
Google Authenticator can be configured using a QR code, or via a "manual entry".
How can I generate the manual entry code with pyotp?
thanks!
Hey I made LuaOTP and COTP (in-development still), which would be nice placed in here on the readme.md.
https://github.com/Hydroque/COTP
https://github.com/Hydroque/LuaOTP
Which is a spin off of this library, COTP is in C, but has CPP headers.
LuaOTP is mostly complete. I need to do a pass through and check it out a bit more before I consider it not in dev.
Hello,
Recently I and my friend @sokokaleb found out that we actually could change the digits and digest that being used in the OTP class, therefore, I want to propose a more general approach for this library so that we could use different digits and digest as we need it for ourselves instead of giving the hard coded value of both of it.
Thanks :)
In order to demonstrate that, I implemented tests for all the values defined in RFC 6238. I also implemented the fix. See: #9
I have the following code in my library:
def get_token(...):
...
msg = struct.pack(">Q", int(time / seconds))
r = hmac.new(secret, msg, sha1).digest()
k = r[19]
idx = k & 0x0f
h = struct.unpack(">L", r[idx:idx + 4])[0] & 0x7fffffff
return h % (10 ** digits), -(time % seconds - seconds)
I'm replacing it with pyotp to simplify things. Unfortunately, I do use that second return parameter, which is the time remaining for the code's validity. I use it to show how long the user's code is still valid for, and to decide when to generate a new one.
I understand why at() doesn't return the time remaining but having it in generate_otp() at the very least would make this functionality possible.
Thanks!
With
otp_key = 'insert your key here'
totp = pyotp.TOTP(otp_key)
otp_pw = totp.now()
Sometimes the Int returned gets a leading zero and that gets interpreted as octal.
That fails every time.
This should return a str of digits instead of an int.
The current license is unclear. The setup.py
sais it is BSD license but the included LICENSE
text is the MIT license.
The line https://github.com/pyauth/pyotp/blame/master/README.rst#L95 puts name
and issuer
in TOTP
constructor, when it should be in the provisioning_uri
method call, and issuer
should be issuer_name
.
Your documentation suggest using qrious - this is fine and works well.
The thing that puzzles me is that the provisioning URI contains the secret key, and yet we send this URI to the client end to be turned into a QR code by qrious. So the secret key isn't secret because it has been sent to the client.
I would have expected that the secret key must never be sent out of the back end - what am I failing to understand?
thanks
According to Key Uri Format there are three more optional arguments: algorithm
, digits
and period
; that if they are set to something different than their defaults: SHA1
, 6
and 30
respectively; they should be encoded in the generated URI.
Those values are set when the class TOTP
is instantiated but are not used by the build_uri
in the utils.py
module.
totp = pyotp.TOTP(user.totp_secret, interval=30)
right after 30 seconds, the totp is invalid.
Can we add some leeway to it? Like 5 seconds?
When the interval argument is not set to 30 when calling pyotp.TOTP(), it appears that any OTP's generated are false when you try to verify them
Hi, I'm using python 2.7 and I installed via pip pyotp, coverage and coveralls. I'm trying to use the TOTP but I get the follow:
Python 2.7.10 (default, Aug 21 2015, 14:42:12) [MSC v.1500 32 bit (Intel)]
Type "copyright", "credits" or "license" for more information.
IPython 5.0.0 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
In [1]: import pyotp
In [2]: totp = pyotp.TOTP("secret")
In [3]: totp.now()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-e7083352b78f> in <module>()
----> 1 totp.now()
c:\dev\python27\lib\site-packages\pyotp\totp.pyc in now(self)
33 @return [Integer] the OTP as an integer
34 """
---> 35 return self.generate_otp(self.timecode(datetime.datetime.now()))
36
37 def verify(self, otp, for_time=None, valid_window=0):
c:\dev\python27\lib\site-packages\pyotp\otp.pyc in generate_otp(self, input)
28 based on the Unix timestamp
29 """
---> 30 hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest)
31 hmac_hash = bytearray(hasher.digest())
32 offset = hmac_hash[-1] & 0xf
c:\dev\python27\lib\site-packages\pyotp\otp.pyc in byte_secret(self)
45 if missing_padding != 0:
46 self.secret += '=' * (8 - missing_padding)
---> 47 return base64.b32decode(self.secret, casefold=True)
48
49 @staticmethod
c:\dev\python27\lib\base64.pyc in b32decode(s, casefold, map01)
240 last = last[:-4]
241 else:
--> 242 raise TypeError('Incorrect padding')
243 parts.append(last)
244 return EMPTYSTRING.join(parts)
TypeError: Incorrect padding
I followed the usage on the docs but saw nothing the relates to that, any help?
Hi!
provisioning_uri
generates a wrong URI, which results in an invalid QR code.
Here's what I did:
import pyotp
totp = pyotp.TOTP('ASFAS75ASDF75889G9AD7S69AS7697AS', digits=8)
totp.provisioning_uri('EU123412341234', issuer_name='Blizzard')
This prints
otpauth://totp/Blizzard:EU123412341234?secret=ASFAS75ASDF75889G9AD7S69AS7697AS&issuer=Blizzard&digits=8
which is missing a :
between the name and ?
. This generates an invalid QR code.
This would be the correct URI:
otpauth://totp/Blizzard:EU123412341234:?secret=ASFAS75ASDF75889G9AD7S69AS7697AS&issuer=Blizzard&digits=8
which works as intended.
QR codes tested with qrencode and andOTP.
Please see jleclanche/python-bna#24 for a current workaround using sed or awk.
Tested on Arch Linux using python-pyotp 2.3.0.
Best Regards
Josef
If the generate token start with 0 and you want verify with the command hotp.verify(TOKEN, COUNTER)
an error occurs :
SyntaxError: invalid token
How to reproduce this :
seed = S53FURSFO47OKDE4 # generate first with pyotp.random_base32()
hotp = pyotp.HOTP(seed)
hotp.at(0) # => 055028
hotp.verify(055028, 0) # => SyntaxError: invalid token
If you try with hotp.at(1)
(with the same seed) and verify next, it's work.
Have you any idea why ? Is it possible to prevent tokens generate with 0 on start ?
Best regards,
Yxoti
Don't have much time these days - give me a shout if you think you can help!
Hi, this is not an issue, more like a discussion, not sure if this is the right medium, you can close this if you don't feel this belongs here.
So i'm using this tool for an integration test on an authentication system. Since this is time based and the intervals are 30 seconds... if a test fires just at the wrong time, it's gonna fail caus' the code is not right.
So i'm wondering how you guys deal with this.
Do you have a server that accepts the current and last code so it's a bit more elastic on token acceptance?
Do you just relaunch your tests when they fail?
Do you use some waiting mechanism if the interval is approaching such as this:
import datetime
import time
import pyotp
class SafeTOTP(pyotp.TOTP):
def __init__(self, *args, **kwargs):
self.time_buffer_to_refresh = kwargs.pop('time_buffer_to_refresh', 2)
super().__init__(*args, **kwargs)
def now(self):
time_to_next_refresh = self.interval - datetime.datetime.now().timestamp() % self.interval
if time_to_next_refresh < self.time_buffer_to_refresh:
time.sleep(time_to_next_refresh)
return super().now()
Thanks for the tool by the way! :)
Consider this test:
import pyotp
from time import sleep
key = pyotp.random_base32()
for i in range(1,10):
otp = pyotp.TOTP(key).now()
sleep(5)
print pyotp.TOTP(key).verify(otp)
Sample output is:
True
False
True
True
True
True
True
False
True
I have seen this in production. I don't know why it fails sometimes. Any idea why? And how I can go about fixing it?
Thank you,
Joseph
Google App Engine does not allow running the subprocess package.
File "/home/c/public/future/backports/__init__.py", line 17, in <module>
from .misc import (ceil,
File "/home/c/public/future/backports/misc.py", line 900, in <module>
from subprocess import check_output
At the moment, Google App Engine does not fully support Python 3, so switching to Python 3 is not currently a solution.
pypy3 support Python 3.2.5 syntax.
on pypy3, try the example and pypy3 show following error
import pyotp
totp = pyotp.TOTP('base32secret3232')
totp.now()
Traceback (most recent call last):
File "", line 1, in
File "/opt/pypy3/site-packages/pyotp/totp.py", line 35, in now
return self.generate_otp(self.timecode(datetime.datetime.now()))
File "/opt/pypy3/site-packages/pyotp/otp.py", line 31, in generate_otp
self.byte_secret(),
File "/opt/pypy3/site-packages/pyotp/otp.py", line 52, in byte_secret
return base64.b32decode(self.secret, casefold=True)
File "/opt/pypy3/lib-python/3/base64.py", line 215, in b32decode
raise TypeError("expected bytes, not %s" % s. _ _ class _ _ . _ _ name _ _ )
TypeError: expected bytes, not str
Running this code results in a hang:
hotp = pyotp.HOTP('base32secret3232')
hotp.at(-1)
The at() method should probably raise an exception if it's passed a negative value.
Hi, I'm working on a project that needs a TOTP hashed by HMAC-SHA-512 any ideas on how i would go about this?
The file .travis.yml
is commited but no connection to TravisCI was created.
The issuer_name parameter is first encoded here and the output of that is HTML encoded again here.
The first filter changes Hello World
to Hello%20World
(which is exactly what it should be) but the second time changes the output of the first filter (Hello%20World
) to Hello%2520World
(escaping the percent sign before the 20).
When authenticator apps receive this, they interpret the %25
and render the remainder. Therefore, by using the uri_provisioning()
method with the issuer_name
string "Hello World"
, the apps display Hello%20World
instead of Hello World
.
Pull request coming soon.
After reading through Google Authenticator's Key URI Formatting Wiki I was glad to find information regarding the issuer label, however, I wasn't aware pyotp provided this option until I started hunting through source code/pull requests (PR #2). I think as easy as it is to configure, this parameter should also be demonstrated under the Google Authenticator Compatible
heading.
Thank you.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.