PyOTP 2.2.1__tar.gz → 2.2.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyotp-2.2.1/src/pyotp.egg-info → pyotp-2.2.5}/PKG-INFO +21 -11
- {pyotp-2.2.1 → pyotp-2.2.5}/README.rst +20 -10
- {pyotp-2.2.1 → pyotp-2.2.5}/setup.cfg +1 -2
- pyotp-2.2.5/setup.py +34 -0
- pyotp-2.2.5/src/pyotp/hotp.py +57 -0
- {pyotp-2.2.1 → pyotp-2.2.5}/src/pyotp/otp.py +14 -11
- {pyotp-2.2.1 → pyotp-2.2.5}/src/pyotp/totp.py +38 -16
- {pyotp-2.2.1 → pyotp-2.2.5}/src/pyotp/utils.py +18 -10
- {pyotp-2.2.1 → pyotp-2.2.5/src/pyotp.egg-info}/PKG-INFO +21 -11
- {pyotp-2.2.1 → pyotp-2.2.5}/test.py +23 -13
- pyotp-2.2.1/setup.py +0 -34
- pyotp-2.2.1/src/pyotp/hotp.py +0 -42
- {pyotp-2.2.1 → pyotp-2.2.5}/LICENSE +0 -0
- {pyotp-2.2.1 → pyotp-2.2.5}/MANIFEST.in +0 -0
- {pyotp-2.2.1 → pyotp-2.2.5}/requirements.txt +0 -0
- {pyotp-2.2.1 → pyotp-2.2.5}/src/pyotp/__init__.py +0 -0
- {pyotp-2.2.1 → pyotp-2.2.5}/src/pyotp/compat.py +0 -0
- {pyotp-2.2.1 → pyotp-2.2.5}/src/pyotp.egg-info/SOURCES.txt +0 -0
- {pyotp-2.2.1 → pyotp-2.2.5}/src/pyotp.egg-info/dependency_links.txt +0 -0
- {pyotp-2.2.1 → pyotp-2.2.5}/src/pyotp.egg-info/not-zip-safe +0 -0
- {pyotp-2.2.1 → pyotp-2.2.5}/src/pyotp.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 1.1
|
|
2
2
|
Name: pyotp
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.5
|
|
4
4
|
Summary: Python One Time Password Library
|
|
5
5
|
Home-page: https://github.com/pyotp/pyotp
|
|
6
6
|
Author: PyOTP contributors
|
|
@@ -20,6 +20,8 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
20
20
|
compatible app. Users can set up auth tokens in their apps easily by using their phone camera to scan `otpauth://
|
|
21
21
|
<https://github.com/google/google-authenticator/wiki/Key-Uri-Format>`_ QR codes provided by PyOTP.
|
|
22
22
|
|
|
23
|
+
We recommend that implementers read the `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_ and `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_ for a high level overview of authentication best practices.
|
|
24
|
+
|
|
23
25
|
Quick overview of using One Time Passwords on your phone
|
|
24
26
|
--------------------------------------------------------
|
|
25
27
|
|
|
@@ -37,7 +39,7 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
37
39
|
Usage
|
|
38
40
|
-----
|
|
39
41
|
|
|
40
|
-
Time
|
|
42
|
+
Time-based OTPs
|
|
41
43
|
~~~~~~~~~~~~~~~
|
|
42
44
|
::
|
|
43
45
|
|
|
@@ -66,23 +68,29 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
66
68
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
67
69
|
::
|
|
68
70
|
|
|
69
|
-
pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator
|
|
71
|
+
pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator and other OTP apps
|
|
70
72
|
|
|
71
73
|
Google Authenticator Compatible
|
|
72
74
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
provisioning
|
|
76
|
+
PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes the
|
|
77
|
+
ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
|
|
78
|
+
|
|
79
|
+
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice@google.com", issuer_name="Secure App")
|
|
80
|
+
|
|
81
|
+
>>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure+App'
|
|
82
|
+
|
|
83
|
+
pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice@google.com", initial_count=0, issuer_name="Secure App")
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
hotp.provisioning_uri("alice@google.com", 0) # => 'otpauth://hotp/alice@google.com?secret=JBSWY3DPEHPK3PXP&counter=0'
|
|
85
|
+
>>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure+App&counter=0'
|
|
79
86
|
|
|
80
|
-
This can then be rendered as a QR Code which can then be scanned
|
|
87
|
+
This URL can then be rendered as a QR Code (for example, using https://github.com/neocotic/qrious) which can then be scanned
|
|
88
|
+
and added to the users list of OTP credentials.
|
|
81
89
|
|
|
82
90
|
Working example
|
|
83
91
|
~~~~~~~~~~~~~~~
|
|
84
92
|
|
|
85
|
-
Scan the following barcode with your phone
|
|
93
|
+
Scan the following barcode with your phone's OTP app (e.g. Google Authenticator):
|
|
86
94
|
|
|
87
95
|
.. image:: http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP
|
|
88
96
|
|
|
@@ -96,13 +104,15 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
96
104
|
~~~~~
|
|
97
105
|
|
|
98
106
|
* `Project home page (GitHub) <https://github.com/pyotp/pyotp>`_
|
|
99
|
-
* `Documentation (Read the Docs) <https://pyotp.readthedocs.
|
|
107
|
+
* `Documentation (Read the Docs) <https://pyotp.readthedocs.io/en/latest/>`_
|
|
100
108
|
* `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
|
|
101
109
|
* `Change log <https://github.com/pyotp/pyotp/blob/master/Changes.rst>`_
|
|
102
110
|
* `RFC 4226: HOTP: An HMAC-Based One-Time Password <https://tools.ietf.org/html/rfc4226>`_
|
|
103
111
|
* `RFC 6238: TOTP: Time-Based One-Time Password Algorithm <https://tools.ietf.org/html/rfc6238>`_
|
|
104
112
|
* `ROTP <https://github.com/mdp/rotp>`_ - Original Ruby OTP library by `Mark Percival <https://github.com/mdp>`_
|
|
105
113
|
* `OTPHP <https://github.com/lelag/otphp>`_ - PHP port of ROTP by `Le Lag <https://github.com/lelag>`_
|
|
114
|
+
* `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_
|
|
115
|
+
* `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_
|
|
106
116
|
|
|
107
117
|
.. image:: https://img.shields.io/travis/pyotp/pyotp.svg
|
|
108
118
|
:target: https://travis-ci.org/pyotp/pyotp
|
|
@@ -113,7 +123,7 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
113
123
|
.. image:: https://img.shields.io/pypi/l/pyotp.svg
|
|
114
124
|
:target: https://pypi.python.org/pypi/pyotp
|
|
115
125
|
.. image:: https://readthedocs.org/projects/pyotp/badge/?version=latest
|
|
116
|
-
:target: https://pyotp.readthedocs.
|
|
126
|
+
:target: https://pyotp.readthedocs.io/
|
|
117
127
|
|
|
118
128
|
Platform: MacOS X
|
|
119
129
|
Platform: Posix
|
|
@@ -12,6 +12,8 @@ Authenticator <https://en.wikipedia.org/wiki/Google_Authenticator>`_, `Authy <ht
|
|
|
12
12
|
compatible app. Users can set up auth tokens in their apps easily by using their phone camera to scan `otpauth://
|
|
13
13
|
<https://github.com/google/google-authenticator/wiki/Key-Uri-Format>`_ QR codes provided by PyOTP.
|
|
14
14
|
|
|
15
|
+
We recommend that implementers read the `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_ and `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_ for a high level overview of authentication best practices.
|
|
16
|
+
|
|
15
17
|
Quick overview of using One Time Passwords on your phone
|
|
16
18
|
--------------------------------------------------------
|
|
17
19
|
|
|
@@ -29,7 +31,7 @@ Installation
|
|
|
29
31
|
Usage
|
|
30
32
|
-----
|
|
31
33
|
|
|
32
|
-
Time
|
|
34
|
+
Time-based OTPs
|
|
33
35
|
~~~~~~~~~~~~~~~
|
|
34
36
|
::
|
|
35
37
|
|
|
@@ -58,23 +60,29 @@ Generating a base32 Secret Key
|
|
|
58
60
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
59
61
|
::
|
|
60
62
|
|
|
61
|
-
pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator
|
|
63
|
+
pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator and other OTP apps
|
|
62
64
|
|
|
63
65
|
Google Authenticator Compatible
|
|
64
66
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
provisioning
|
|
68
|
+
PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes the
|
|
69
|
+
ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
|
|
70
|
+
|
|
71
|
+
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice@google.com", issuer_name="Secure App")
|
|
72
|
+
|
|
73
|
+
>>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure+App'
|
|
74
|
+
|
|
75
|
+
pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice@google.com", initial_count=0, issuer_name="Secure App")
|
|
68
76
|
|
|
69
|
-
|
|
70
|
-
hotp.provisioning_uri("alice@google.com", 0) # => 'otpauth://hotp/alice@google.com?secret=JBSWY3DPEHPK3PXP&counter=0'
|
|
77
|
+
>>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure+App&counter=0'
|
|
71
78
|
|
|
72
|
-
This can then be rendered as a QR Code which can then be scanned
|
|
79
|
+
This URL can then be rendered as a QR Code (for example, using https://github.com/neocotic/qrious) which can then be scanned
|
|
80
|
+
and added to the users list of OTP credentials.
|
|
73
81
|
|
|
74
82
|
Working example
|
|
75
83
|
~~~~~~~~~~~~~~~
|
|
76
84
|
|
|
77
|
-
Scan the following barcode with your phone
|
|
85
|
+
Scan the following barcode with your phone's OTP app (e.g. Google Authenticator):
|
|
78
86
|
|
|
79
87
|
.. image:: http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP
|
|
80
88
|
|
|
@@ -88,13 +96,15 @@ Links
|
|
|
88
96
|
~~~~~
|
|
89
97
|
|
|
90
98
|
* `Project home page (GitHub) <https://github.com/pyotp/pyotp>`_
|
|
91
|
-
* `Documentation (Read the Docs) <https://pyotp.readthedocs.
|
|
99
|
+
* `Documentation (Read the Docs) <https://pyotp.readthedocs.io/en/latest/>`_
|
|
92
100
|
* `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
|
|
93
101
|
* `Change log <https://github.com/pyotp/pyotp/blob/master/Changes.rst>`_
|
|
94
102
|
* `RFC 4226: HOTP: An HMAC-Based One-Time Password <https://tools.ietf.org/html/rfc4226>`_
|
|
95
103
|
* `RFC 6238: TOTP: Time-Based One-Time Password Algorithm <https://tools.ietf.org/html/rfc6238>`_
|
|
96
104
|
* `ROTP <https://github.com/mdp/rotp>`_ - Original Ruby OTP library by `Mark Percival <https://github.com/mdp>`_
|
|
97
105
|
* `OTPHP <https://github.com/lelag/otphp>`_ - PHP port of ROTP by `Le Lag <https://github.com/lelag>`_
|
|
106
|
+
* `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_
|
|
107
|
+
* `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_
|
|
98
108
|
|
|
99
109
|
.. image:: https://img.shields.io/travis/pyotp/pyotp.svg
|
|
100
110
|
:target: https://travis-ci.org/pyotp/pyotp
|
|
@@ -105,4 +115,4 @@ Links
|
|
|
105
115
|
.. image:: https://img.shields.io/pypi/l/pyotp.svg
|
|
106
116
|
:target: https://pypi.python.org/pypi/pyotp
|
|
107
117
|
.. image:: https://readthedocs.org/projects/pyotp/badge/?version=latest
|
|
108
|
-
:target: https://pyotp.readthedocs.
|
|
118
|
+
:target: https://pyotp.readthedocs.io/
|
pyotp-2.2.5/setup.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from setuptools import setup
|
|
5
|
+
|
|
6
|
+
install_requires = [line.rstrip() for line in open(os.path.join(os.path.dirname(__file__), "requirements.txt"))]
|
|
7
|
+
|
|
8
|
+
setup(
|
|
9
|
+
name="pyotp",
|
|
10
|
+
version="2.2.5",
|
|
11
|
+
url="https://github.com/pyotp/pyotp",
|
|
12
|
+
license="BSD License",
|
|
13
|
+
author="PyOTP contributors",
|
|
14
|
+
author_email="kislyuk@gmail.com",
|
|
15
|
+
description="Python One Time Password Library",
|
|
16
|
+
long_description=open("README.rst").read(),
|
|
17
|
+
install_requires=install_requires,
|
|
18
|
+
packages=["pyotp"],
|
|
19
|
+
package_dir={"": "src"},
|
|
20
|
+
platforms=["MacOS X", "Posix"],
|
|
21
|
+
zip_safe=False,
|
|
22
|
+
test_suite="test",
|
|
23
|
+
classifiers=[
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"License :: OSI Approved :: BSD License",
|
|
26
|
+
"Operating System :: MacOS :: MacOS X",
|
|
27
|
+
"Operating System :: POSIX",
|
|
28
|
+
"Programming Language :: Python",
|
|
29
|
+
"Programming Language :: Python :: 2.7",
|
|
30
|
+
"Programming Language :: Python :: 3.3",
|
|
31
|
+
"Programming Language :: Python :: 3.4",
|
|
32
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
33
|
+
]
|
|
34
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
2
|
+
|
|
3
|
+
from . import utils
|
|
4
|
+
from .otp import OTP
|
|
5
|
+
from .compat import str
|
|
6
|
+
|
|
7
|
+
class HOTP(OTP):
|
|
8
|
+
"""
|
|
9
|
+
Handler for HMAC-based OTP counters.
|
|
10
|
+
"""
|
|
11
|
+
def at(self, count):
|
|
12
|
+
"""
|
|
13
|
+
Generates the OTP for the given count.
|
|
14
|
+
|
|
15
|
+
:param count: the OTP HMAC counter
|
|
16
|
+
:type count: int
|
|
17
|
+
:returns: OTP
|
|
18
|
+
:rtype: str
|
|
19
|
+
"""
|
|
20
|
+
return self.generate_otp(count)
|
|
21
|
+
|
|
22
|
+
def verify(self, otp, counter):
|
|
23
|
+
"""
|
|
24
|
+
Verifies the OTP passed in against the current time OTP.
|
|
25
|
+
|
|
26
|
+
:param otp: the OTP to check against
|
|
27
|
+
:type otp: str
|
|
28
|
+
:param count: the OTP HMAC counter
|
|
29
|
+
:type count: int
|
|
30
|
+
"""
|
|
31
|
+
return utils.strings_equal(str(otp), str(self.at(counter)))
|
|
32
|
+
|
|
33
|
+
def provisioning_uri(self, name, initial_count=0, issuer_name=None):
|
|
34
|
+
"""
|
|
35
|
+
Returns the provisioning URI for the OTP. This can then be
|
|
36
|
+
encoded in a QR Code and used to provision an OTP app like
|
|
37
|
+
Google Authenticator. See
|
|
38
|
+
https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
|
39
|
+
for more details.
|
|
40
|
+
|
|
41
|
+
:param name: name of the user account
|
|
42
|
+
:type name: str
|
|
43
|
+
:param initial_count: starting HMAC counter value, defaults to 0
|
|
44
|
+
:type initial_count: int
|
|
45
|
+
:param issuer_name: the name of the OTP issuer; this will be the
|
|
46
|
+
organization title of the OTP entry in Authenticator
|
|
47
|
+
:returns: provisioning URI
|
|
48
|
+
:rtype: str
|
|
49
|
+
"""
|
|
50
|
+
return utils.build_uri(
|
|
51
|
+
self.secret,
|
|
52
|
+
name,
|
|
53
|
+
initial_count=initial_count,
|
|
54
|
+
issuer_name=issuer_name,
|
|
55
|
+
algorithm=self.digest().name,
|
|
56
|
+
digits=self.digits
|
|
57
|
+
)
|
|
@@ -6,16 +6,17 @@ import hmac
|
|
|
6
6
|
from .compat import str
|
|
7
7
|
|
|
8
8
|
class OTP(object):
|
|
9
|
+
"""
|
|
10
|
+
Base class for OTP handlers.
|
|
11
|
+
"""
|
|
9
12
|
def __init__(self, s, digits=6, digest=hashlib.sha1):
|
|
10
13
|
"""
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Google Authenticate only supports 'sha1' currently
|
|
18
|
-
@returns [OTP] OTP instantiation
|
|
14
|
+
:param s: secret in base32 format
|
|
15
|
+
:type s: str
|
|
16
|
+
:param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more.
|
|
17
|
+
:type digits: int
|
|
18
|
+
:param digest: digest function to use in the HMAC (expected to be sha1)
|
|
19
|
+
:type digest: callable
|
|
19
20
|
"""
|
|
20
21
|
self.digits = digits
|
|
21
22
|
self.digest = digest
|
|
@@ -23,10 +24,12 @@ class OTP(object):
|
|
|
23
24
|
|
|
24
25
|
def generate_otp(self, input):
|
|
25
26
|
"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
:param input: the HMAC counter value to use as the OTP input.
|
|
28
|
+
Usually either the counter, or the computed integer based on the Unix timestamp
|
|
29
|
+
:type input: int
|
|
29
30
|
"""
|
|
31
|
+
if input < 0:
|
|
32
|
+
raise ValueError('input must be positive integer')
|
|
30
33
|
hasher = hmac.new(self.byte_secret(), self.int_to_bytestring(input), self.digest)
|
|
31
34
|
hmac_hash = bytearray(hasher.digest())
|
|
32
35
|
offset = hmac_hash[-1] & 0xf
|
|
@@ -8,20 +8,27 @@ from .otp import OTP
|
|
|
8
8
|
from .compat import str
|
|
9
9
|
|
|
10
10
|
class TOTP(OTP):
|
|
11
|
+
"""
|
|
12
|
+
Handler for time-based OTP counters.
|
|
13
|
+
"""
|
|
11
14
|
def __init__(self, *args, **kwargs):
|
|
12
15
|
"""
|
|
13
|
-
|
|
14
|
-
for OTP This defaults to 30
|
|
16
|
+
:param interval: the time interval in seconds
|
|
17
|
+
for OTP. This defaults to 30.
|
|
18
|
+
:type interval: int
|
|
15
19
|
"""
|
|
16
20
|
self.interval = kwargs.pop('interval', 30)
|
|
17
21
|
super(TOTP, self).__init__(*args, **kwargs)
|
|
18
22
|
|
|
19
23
|
def at(self, for_time, counter_offset=0):
|
|
20
24
|
"""
|
|
21
|
-
Accepts either a Unix timestamp integer or a
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
Accepts either a Unix timestamp integer or a datetime object.
|
|
26
|
+
|
|
27
|
+
:param for_time: the time to generate an OTP for
|
|
28
|
+
:type for_time: int or datetime
|
|
29
|
+
:param counter_offset: the amount of ticks to add to the time counter
|
|
30
|
+
:returns: OTP value
|
|
31
|
+
:rtype: str
|
|
25
32
|
"""
|
|
26
33
|
if not isinstance(for_time, datetime.datetime):
|
|
27
34
|
for_time = datetime.datetime.fromtimestamp(int(for_time))
|
|
@@ -30,16 +37,24 @@ class TOTP(OTP):
|
|
|
30
37
|
def now(self):
|
|
31
38
|
"""
|
|
32
39
|
Generate the current time OTP
|
|
33
|
-
|
|
40
|
+
|
|
41
|
+
:returns: OTP value
|
|
42
|
+
:rtype: str
|
|
34
43
|
"""
|
|
35
44
|
return self.generate_otp(self.timecode(datetime.datetime.now()))
|
|
36
45
|
|
|
37
46
|
def verify(self, otp, for_time=None, valid_window=0):
|
|
38
47
|
"""
|
|
39
|
-
Verifies the OTP passed in against the current time OTP
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
Verifies the OTP passed in against the current time OTP.
|
|
49
|
+
|
|
50
|
+
:param otp: the OTP to check against
|
|
51
|
+
:type otp: str
|
|
52
|
+
:param for_time: Time to check OTP at (defaults to now)
|
|
53
|
+
:type for_time: int or datetime
|
|
54
|
+
:param valid_window: extends the validity to this many counter ticks before and after the current one
|
|
55
|
+
:type valid_window: int
|
|
56
|
+
:returns: True if verification succeeded, False otherwise
|
|
57
|
+
:rtype: bool
|
|
43
58
|
"""
|
|
44
59
|
if for_time is None:
|
|
45
60
|
for_time = datetime.datetime.now()
|
|
@@ -54,11 +69,18 @@ class TOTP(OTP):
|
|
|
54
69
|
|
|
55
70
|
def provisioning_uri(self, name, issuer_name=None):
|
|
56
71
|
"""
|
|
57
|
-
Returns the provisioning URI for the OTP
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
Returns the provisioning URI for the OTP. This can then be
|
|
73
|
+
encoded in a QR Code and used to provision an OTP app like
|
|
74
|
+
Google Authenticator. See
|
|
75
|
+
https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
|
76
|
+
for more details.
|
|
77
|
+
|
|
78
|
+
:param name: name of the user account
|
|
79
|
+
:type name: str
|
|
80
|
+
:param issuer_name: the name of the OTP issuer; this will be the
|
|
81
|
+
organization title of the OTP entry in Authenticator
|
|
82
|
+
:returns: provisioning URI
|
|
83
|
+
:rtype: str
|
|
62
84
|
"""
|
|
63
85
|
return utils.build_uri(self.secret, name, issuer_name=issuer_name,
|
|
64
86
|
algorithm=self.digest().name,
|
|
@@ -25,17 +25,25 @@ def build_uri(secret, name, initial_count=None, issuer_name=None,
|
|
|
25
25
|
See also:
|
|
26
26
|
https://github.com/google/google-authenticator/wiki/Key-Uri-Format
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
:param secret: the hotp/totp secret used to generate the URI
|
|
29
|
+
:type secret: str
|
|
30
|
+
:param name: name of the account
|
|
31
|
+
:type name: str
|
|
32
|
+
:param initial_count: starting counter value, defaults to None.
|
|
31
33
|
If none, the OTP type will be assumed as TOTP.
|
|
32
|
-
|
|
34
|
+
:type initial_count: int
|
|
35
|
+
:param issuer_name: the name of the OTP issuer; this will be the
|
|
33
36
|
organization title of the OTP entry in Authenticator
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
:type issuer_name: str
|
|
38
|
+
:param algorithm: the algorithm used in the OTP generation.
|
|
39
|
+
:type algorithm: str
|
|
40
|
+
:param digits: the length of the OTP generated code.
|
|
41
|
+
:type digits: int
|
|
42
|
+
:param period: the number of seconds the OTP generator is set to
|
|
37
43
|
expire every code.
|
|
38
|
-
|
|
44
|
+
:type period: int
|
|
45
|
+
:returns: provisioning uri
|
|
46
|
+
:rtype: str
|
|
39
47
|
"""
|
|
40
48
|
# initial_count may be 0 as a valid param
|
|
41
49
|
is_initial_count_present = (initial_count is not None)
|
|
@@ -46,14 +54,14 @@ def build_uri(secret, name, initial_count=None, issuer_name=None,
|
|
|
46
54
|
is_period_set = (period is not None and period != 30)
|
|
47
55
|
|
|
48
56
|
otp_type = 'hotp' if is_initial_count_present else 'totp'
|
|
49
|
-
base_uri = 'otpauth://{}/{}?{}'
|
|
57
|
+
base_uri = 'otpauth://{0}/{1}?{2}'
|
|
50
58
|
|
|
51
59
|
url_args = {'secret': secret}
|
|
52
60
|
|
|
53
61
|
label = quote(name)
|
|
54
62
|
if issuer_name is not None:
|
|
55
63
|
label = quote(issuer_name) + ':' + label
|
|
56
|
-
url_args['issuer'] = issuer_name
|
|
64
|
+
url_args['issuer'] = quote(issuer_name)
|
|
57
65
|
|
|
58
66
|
if is_initial_count_present:
|
|
59
67
|
url_args['counter'] = initial_count
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 1.1
|
|
2
2
|
Name: pyotp
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.5
|
|
4
4
|
Summary: Python One Time Password Library
|
|
5
5
|
Home-page: https://github.com/pyotp/pyotp
|
|
6
6
|
Author: PyOTP contributors
|
|
@@ -20,6 +20,8 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
20
20
|
compatible app. Users can set up auth tokens in their apps easily by using their phone camera to scan `otpauth://
|
|
21
21
|
<https://github.com/google/google-authenticator/wiki/Key-Uri-Format>`_ QR codes provided by PyOTP.
|
|
22
22
|
|
|
23
|
+
We recommend that implementers read the `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_ and `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_ for a high level overview of authentication best practices.
|
|
24
|
+
|
|
23
25
|
Quick overview of using One Time Passwords on your phone
|
|
24
26
|
--------------------------------------------------------
|
|
25
27
|
|
|
@@ -37,7 +39,7 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
37
39
|
Usage
|
|
38
40
|
-----
|
|
39
41
|
|
|
40
|
-
Time
|
|
42
|
+
Time-based OTPs
|
|
41
43
|
~~~~~~~~~~~~~~~
|
|
42
44
|
::
|
|
43
45
|
|
|
@@ -66,23 +68,29 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
66
68
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
67
69
|
::
|
|
68
70
|
|
|
69
|
-
pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator
|
|
71
|
+
pyotp.random_base32() # returns a 16 character base32 secret. Compatible with Google Authenticator and other OTP apps
|
|
70
72
|
|
|
71
73
|
Google Authenticator Compatible
|
|
72
74
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
provisioning
|
|
76
|
+
PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes the
|
|
77
|
+
ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
|
|
78
|
+
|
|
79
|
+
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice@google.com", issuer_name="Secure App")
|
|
80
|
+
|
|
81
|
+
>>> 'otpauth://totp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure+App'
|
|
82
|
+
|
|
83
|
+
pyotp.hotp.HOTP('JBSWY3DPEHPK3PXP').provisioning_uri("alice@google.com", initial_count=0, issuer_name="Secure App")
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
hotp.provisioning_uri("alice@google.com", 0) # => 'otpauth://hotp/alice@google.com?secret=JBSWY3DPEHPK3PXP&counter=0'
|
|
85
|
+
>>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure+App&counter=0'
|
|
79
86
|
|
|
80
|
-
This can then be rendered as a QR Code which can then be scanned
|
|
87
|
+
This URL can then be rendered as a QR Code (for example, using https://github.com/neocotic/qrious) which can then be scanned
|
|
88
|
+
and added to the users list of OTP credentials.
|
|
81
89
|
|
|
82
90
|
Working example
|
|
83
91
|
~~~~~~~~~~~~~~~
|
|
84
92
|
|
|
85
|
-
Scan the following barcode with your phone
|
|
93
|
+
Scan the following barcode with your phone's OTP app (e.g. Google Authenticator):
|
|
86
94
|
|
|
87
95
|
.. image:: http://chart.apis.google.com/chart?cht=qr&chs=250x250&chl=otpauth%3A%2F%2Ftotp%2Falice%40google.com%3Fsecret%3DJBSWY3DPEHPK3PXP
|
|
88
96
|
|
|
@@ -96,13 +104,15 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
96
104
|
~~~~~
|
|
97
105
|
|
|
98
106
|
* `Project home page (GitHub) <https://github.com/pyotp/pyotp>`_
|
|
99
|
-
* `Documentation (Read the Docs) <https://pyotp.readthedocs.
|
|
107
|
+
* `Documentation (Read the Docs) <https://pyotp.readthedocs.io/en/latest/>`_
|
|
100
108
|
* `Package distribution (PyPI) <https://pypi.python.org/pypi/pyotp>`_
|
|
101
109
|
* `Change log <https://github.com/pyotp/pyotp/blob/master/Changes.rst>`_
|
|
102
110
|
* `RFC 4226: HOTP: An HMAC-Based One-Time Password <https://tools.ietf.org/html/rfc4226>`_
|
|
103
111
|
* `RFC 6238: TOTP: Time-Based One-Time Password Algorithm <https://tools.ietf.org/html/rfc6238>`_
|
|
104
112
|
* `ROTP <https://github.com/mdp/rotp>`_ - Original Ruby OTP library by `Mark Percival <https://github.com/mdp>`_
|
|
105
113
|
* `OTPHP <https://github.com/lelag/otphp>`_ - PHP port of ROTP by `Le Lag <https://github.com/lelag>`_
|
|
114
|
+
* `OWASP Authentication Cheat Sheet <https://www.owasp.org/index.php/Authentication_Cheat_Sheet>`_
|
|
115
|
+
* `NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_
|
|
106
116
|
|
|
107
117
|
.. image:: https://img.shields.io/travis/pyotp/pyotp.svg
|
|
108
118
|
:target: https://travis-ci.org/pyotp/pyotp
|
|
@@ -113,7 +123,7 @@ Description: PyOTP - The Python One-Time Password Library
|
|
|
113
123
|
.. image:: https://img.shields.io/pypi/l/pyotp.svg
|
|
114
124
|
:target: https://pypi.python.org/pypi/pyotp
|
|
115
125
|
.. image:: https://readthedocs.org/projects/pyotp/badge/?version=latest
|
|
116
|
-
:target: https://pyotp.readthedocs.
|
|
126
|
+
:target: https://pyotp.readthedocs.io/
|
|
117
127
|
|
|
118
128
|
Platform: MacOS X
|
|
119
129
|
Platform: Posix
|
|
@@ -3,12 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
5
5
|
|
|
6
|
-
import base64
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
import os
|
|
10
|
-
import sys
|
|
11
|
-
import unittest
|
|
6
|
+
import base64, datetime, hashlib, os, sys, unittest
|
|
7
|
+
from warnings import warn
|
|
8
|
+
|
|
12
9
|
try:
|
|
13
10
|
from urllib.parse import urlparse, parse_qsl
|
|
14
11
|
except ImportError:
|
|
@@ -33,6 +30,11 @@ class HOTPExampleValuesFromTheRFC(unittest.TestCase):
|
|
|
33
30
|
self.assertEqual(hotp.at(8), '399871')
|
|
34
31
|
self.assertEqual(hotp.at(9), '520489')
|
|
35
32
|
|
|
33
|
+
def test_invalid_input(self):
|
|
34
|
+
hotp = pyotp.HOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
|
|
35
|
+
with self.assertRaises(ValueError):
|
|
36
|
+
hotp.at(-1)
|
|
37
|
+
|
|
36
38
|
def test_verify_otp_reuse(self):
|
|
37
39
|
hotp = pyotp.HOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
|
|
38
40
|
self.assertTrue(hotp.verify('520489', 9))
|
|
@@ -65,7 +67,7 @@ class HOTPExampleValuesFromTheRFC(unittest.TestCase):
|
|
|
65
67
|
self.assertEqual(url.path, '/FooCorp%21:mark%40percival')
|
|
66
68
|
self.assertEqual(dict(parse_qsl(url.query)),
|
|
67
69
|
{'secret': 'wrn3pqx5uqxqvnqr', 'counter': '0',
|
|
68
|
-
'issuer': 'FooCorp
|
|
70
|
+
'issuer': 'FooCorp%21'})
|
|
69
71
|
|
|
70
72
|
key = 'c7uxuqhgflpw7oruedmglbrk7u6242vb'
|
|
71
73
|
hotp = pyotp.HOTP(key, digits=8, digest=hashlib.sha256)
|
|
@@ -81,14 +83,14 @@ class HOTPExampleValuesFromTheRFC(unittest.TestCase):
|
|
|
81
83
|
|
|
82
84
|
hotp = pyotp.HOTP(key, digits=8)
|
|
83
85
|
url = urlparse(
|
|
84
|
-
hotp.provisioning_uri('baco@peperina', issuer_name='
|
|
86
|
+
hotp.provisioning_uri('baco@peperina', issuer_name='Foo Corp',
|
|
85
87
|
initial_count=10))
|
|
86
88
|
self.assertEqual(url.scheme, 'otpauth')
|
|
87
89
|
self.assertEqual(url.netloc, 'hotp')
|
|
88
|
-
self.assertEqual(url.path, '/
|
|
90
|
+
self.assertEqual(url.path, '/Foo%20Corp:baco%40peperina')
|
|
89
91
|
self.assertEqual(dict(parse_qsl(url.query)),
|
|
90
92
|
{'secret': 'c7uxuqhgflpw7oruedmglbrk7u6242vb',
|
|
91
|
-
'counter': '10', 'issuer': '
|
|
93
|
+
'counter': '10', 'issuer': 'Foo%20Corp',
|
|
92
94
|
'digits': '8'})
|
|
93
95
|
|
|
94
96
|
def test_other_secret(self):
|
|
@@ -137,9 +139,9 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
|
|
|
137
139
|
for digest, secret in self.RFC_VALUES:
|
|
138
140
|
totp = pyotp.TOTP(base64.b32encode(secret), 8, digest)
|
|
139
141
|
for utime, code in self.RFC_VALUES[(digest, secret)]:
|
|
140
|
-
# 32-bit platforms use native functions to handle timestamps, so they fail this test
|
|
141
|
-
# (and will fail after 19 January 2038)
|
|
142
142
|
if utime > sys.maxsize:
|
|
143
|
+
warn("32-bit platforms use native functions to handle timestamps, so they fail this test" +
|
|
144
|
+
" (and will fail after 19 January 2038)")
|
|
143
145
|
continue
|
|
144
146
|
value = totp.at(utime)
|
|
145
147
|
msg = "%s != %s (%s, time=%d)"
|
|
@@ -165,6 +167,14 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
|
|
|
165
167
|
with Timecop(1297553958 + 30):
|
|
166
168
|
self.assertFalse(totp.verify('102705'))
|
|
167
169
|
|
|
170
|
+
def test_input_before_epoch(self):
|
|
171
|
+
totp = pyotp.TOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
|
|
172
|
+
# -1 and -29.5 round down to 0 (epoch)
|
|
173
|
+
self.assertEqual(totp.at(-1), '755224')
|
|
174
|
+
self.assertEqual(totp.at(-29.5), '755224')
|
|
175
|
+
with self.assertRaises(ValueError):
|
|
176
|
+
totp.at(-30)
|
|
177
|
+
|
|
168
178
|
def test_validate_totp_with_digit_length(self):
|
|
169
179
|
totp = pyotp.TOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')
|
|
170
180
|
with Timecop(1111111111):
|
|
@@ -189,7 +199,7 @@ class TOTPExampleValuesFromTheRFC(unittest.TestCase):
|
|
|
189
199
|
self.assertEqual(url.path, '/FooCorp%21:mark%40percival')
|
|
190
200
|
self.assertEqual(dict(parse_qsl(url.query)),
|
|
191
201
|
{'secret': 'wrn3pqx5uqxqvnqr',
|
|
192
|
-
'issuer': 'FooCorp
|
|
202
|
+
'issuer': 'FooCorp%21'})
|
|
193
203
|
|
|
194
204
|
key = 'c7uxuqhgflpw7oruedmglbrk7u6242vb'
|
|
195
205
|
totp = pyotp.TOTP(key, digits=8, interval=60, digest=hashlib.sha256)
|
pyotp-2.2.1/setup.py
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from setuptools import setup
|
|
5
|
-
|
|
6
|
-
install_requires = [line.rstrip() for line in open(os.path.join(os.path.dirname(__file__), "requirements.txt"))]
|
|
7
|
-
|
|
8
|
-
setup(
|
|
9
|
-
name='pyotp',
|
|
10
|
-
version='2.2.1',
|
|
11
|
-
url='https://github.com/pyotp/pyotp',
|
|
12
|
-
license='BSD License',
|
|
13
|
-
author='PyOTP contributors',
|
|
14
|
-
author_email='kislyuk@gmail.com',
|
|
15
|
-
description='Python One Time Password Library',
|
|
16
|
-
long_description=open('README.rst').read(),
|
|
17
|
-
install_requires=install_requires,
|
|
18
|
-
packages=['pyotp'],
|
|
19
|
-
package_dir={'': 'src'},
|
|
20
|
-
platforms=['MacOS X', 'Posix'],
|
|
21
|
-
zip_safe=False,
|
|
22
|
-
test_suite="test",
|
|
23
|
-
classifiers=[
|
|
24
|
-
'Intended Audience :: Developers',
|
|
25
|
-
'License :: OSI Approved :: BSD License',
|
|
26
|
-
'Operating System :: MacOS :: MacOS X',
|
|
27
|
-
'Operating System :: POSIX',
|
|
28
|
-
'Programming Language :: Python',
|
|
29
|
-
'Programming Language :: Python :: 2.7',
|
|
30
|
-
'Programming Language :: Python :: 3.3',
|
|
31
|
-
'Programming Language :: Python :: 3.4',
|
|
32
|
-
'Topic :: Software Development :: Libraries :: Python Modules'
|
|
33
|
-
]
|
|
34
|
-
)
|
pyotp-2.2.1/src/pyotp/hotp.py
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from __future__ import absolute_import, division, print_function, unicode_literals
|
|
2
|
-
|
|
3
|
-
from . import utils
|
|
4
|
-
from .otp import OTP
|
|
5
|
-
from .compat import str
|
|
6
|
-
|
|
7
|
-
class HOTP(OTP):
|
|
8
|
-
def at(self, count):
|
|
9
|
-
"""
|
|
10
|
-
Generates the OTP for the given count
|
|
11
|
-
@param [Integer] count counter
|
|
12
|
-
@returns [Integer] OTP
|
|
13
|
-
"""
|
|
14
|
-
return self.generate_otp(count)
|
|
15
|
-
|
|
16
|
-
def verify(self, otp, counter):
|
|
17
|
-
"""
|
|
18
|
-
Verifies the OTP passed in against the current time OTP
|
|
19
|
-
@param [String/Integer] otp the OTP to check against
|
|
20
|
-
@param [Integer] counter the counter of the OTP
|
|
21
|
-
"""
|
|
22
|
-
return utils.strings_equal(str(otp), str(self.at(counter)))
|
|
23
|
-
|
|
24
|
-
def provisioning_uri(self, name, initial_count=0, issuer_name=None):
|
|
25
|
-
"""
|
|
26
|
-
Returns the provisioning URI for the OTP
|
|
27
|
-
This can then be encoded in a QR Code and used
|
|
28
|
-
to provision the Google Authenticator app
|
|
29
|
-
@param [String] name of the account
|
|
30
|
-
@param [Integer] initial_count starting counter value, defaults to 0
|
|
31
|
-
@param [String] the name of the OTP issuer; this will be the
|
|
32
|
-
organization title of the OTP entry in Authenticator
|
|
33
|
-
@return [String] provisioning uri
|
|
34
|
-
"""
|
|
35
|
-
return utils.build_uri(
|
|
36
|
-
self.secret,
|
|
37
|
-
name,
|
|
38
|
-
initial_count=initial_count,
|
|
39
|
-
issuer_name=issuer_name,
|
|
40
|
-
algorithm=self.digest().name,
|
|
41
|
-
digits=self.digits
|
|
42
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|