Coder Social home page Coder Social logo

pyotp's People

Contributors

adferrand avatar alexfikl avatar asweigart avatar b4stien avatar baco avatar benjdevries avatar cclauss avatar changaco avatar cxong avatar ddboline avatar einfachirgendwer0815 avatar eivanov avatar eugene-eeo avatar francois4224 avatar gabrielsroka avatar jamesob avatar jboning avatar jbwdevries avatar jwilk avatar killthekitten avatar kislyuk avatar kunyan avatar marcobiscaro2112 avatar nathforge avatar philshem avatar simonseo avatar thelastproject avatar xncbf avatar zeevro avatar ziima avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyotp's Issues

random_base32 function uses non-cryptographic generator

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.

pyotp/urllib.quote encoding for spaces incompatible with some apps

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.

Small change would allow using with Python 2.6

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}'

Expiration date

How can I get expiration date for TOTP? totp.now() without this information is a bit wired...

Using provisioning_uri occaisionally mixes up secret & issuer

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?

Document the need to prevent OTP reuse and maybe provide suggested solutions

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.

TypeError: comparing strings with non-ASCII characters is not supported

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.

Please create a new release 2.0.2

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.

the example doesn't work in google authenticator.

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?

Supporting Python3.6

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)

Timing resistance fix

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

Documentation example for TOTP is misleading

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.

Is this library still supposed to work?

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.

Support timezone aware datetime for TOTP

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)

provisioning_uri doesn't work with unicode in python2

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?

Authenticate with Pyotp

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?

Add cli script

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.

Support for digits and digest that being used in the OTP class

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 :)

Return time remaining in generate_otp()

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!

Int return from .now()

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.

License unclear

The current license is unclear. The setup.py sais it is BSD license but the included LICENSE text is the MIT license.

Can you help me understand the secret please?

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

Missing arguments in generated provisioning URI

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.

Leeway for totp

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?

TOTP.now() returns a Type Error

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?

provisioning_uri generates wrong URI

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

Invalid token if it start with 0 - HOTP

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

Maintainer needed

Don't have much time these days - give me a shout if you think you can help!

How to ensure we're not between 2 intervals

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.

  1. Do you have a server that accepts the current and last code so it's a bit more elastic on token acceptance?

  2. Do you just relaunch your tests when they fail?

  3. 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! :)

Support resync in TOTP validation

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

It does not work on Google App Engine

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.

not support pypy3

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

Connect to TravisCI

The file .travis.yml is commited but no connection to TravisCI was created.

Double URL encoding in issuer_name param to uri_provisioning

Version: Latest (v.2.2.5)

The issuer_name parameter is first encoded here and the output of that is HTML encoded again here.

capture

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.

README doesn't show Issuer option for provisioning_uri

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.