PyOTP 2.7.0__tar.gz → 2.9.0__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.7.0/src/pyotp.egg-info → pyotp-2.9.0}/PKG-INFO +45 -16
- {pyotp-2.7.0 → pyotp-2.9.0}/README.rst +37 -13
- {pyotp-2.7.0 → pyotp-2.9.0}/pyproject.toml +4 -0
- pyotp-2.9.0/setup.cfg +4 -0
- {pyotp-2.7.0 → pyotp-2.9.0}/setup.py +12 -3
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp/__init__.py +16 -4
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp/contrib/steam.py +8 -1
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp/hotp.py +5 -2
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp/otp.py +4 -5
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp/totp.py +8 -3
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp/utils.py +1 -1
- {pyotp-2.7.0 → pyotp-2.9.0/src/pyotp.egg-info}/PKG-INFO +45 -16
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp.egg-info/SOURCES.txt +1 -1
- pyotp-2.9.0/src/pyotp.egg-info/requires.txt +6 -0
- {pyotp-2.7.0 → pyotp-2.9.0}/test.py +21 -0
- pyotp-2.7.0/setup.cfg +0 -8
- {pyotp-2.7.0 → pyotp-2.9.0}/LICENSE +0 -0
- {pyotp-2.7.0 → pyotp-2.9.0}/MANIFEST.in +0 -0
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp/compat.py +0 -0
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp/contrib/__init__.py +0 -0
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp/py.typed +0 -0
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp.egg-info/dependency_links.txt +0 -0
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp.egg-info/not-zip-safe +0 -0
- {pyotp-2.7.0 → pyotp-2.9.0}/src/pyotp.egg-info/top_level.txt +0 -0
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyotp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.0
|
|
4
4
|
Summary: Python One Time Password Library
|
|
5
5
|
Home-page: https://github.com/pyotp/pyotp
|
|
6
6
|
Author: PyOTP contributors
|
|
7
7
|
Author-email: kislyuk@gmail.com
|
|
8
8
|
License: MIT License
|
|
9
|
+
Project-URL: Documentation, https://pyauth.github.io/pyotp
|
|
10
|
+
Project-URL: Source Code, https://github.com/pyauth/pyotp
|
|
11
|
+
Project-URL: Issue Tracker, https://github.com/pyauth/pyotp/issues
|
|
12
|
+
Project-URL: Change Log, https://github.com/pyauth/pyotp/blob/master/Changes.rst
|
|
9
13
|
Platform: MacOS X
|
|
10
14
|
Platform: Posix
|
|
11
15
|
Classifier: Intended Audience :: Developers
|
|
@@ -14,13 +18,14 @@ Classifier: Operating System :: MacOS :: MacOS X
|
|
|
14
18
|
Classifier: Operating System :: POSIX
|
|
15
19
|
Classifier: Programming Language :: Python
|
|
16
20
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
18
21
|
Classifier: Programming Language :: Python :: 3.7
|
|
19
22
|
Classifier: Programming Language :: Python :: 3.8
|
|
20
23
|
Classifier: Programming Language :: Python :: 3.9
|
|
21
24
|
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Requires-Python: >=3.
|
|
27
|
+
Requires-Python: >=3.7
|
|
28
|
+
Provides-Extra: test
|
|
24
29
|
License-File: LICENSE
|
|
25
30
|
|
|
26
31
|
PyOTP - The Python One-Time Password Library
|
|
@@ -43,19 +48,21 @@ minimum, application implementers should follow this checklist:
|
|
|
43
48
|
|
|
44
49
|
- Ensure transport confidentiality by using HTTPS
|
|
45
50
|
- Ensure HOTP/TOTP secret confidentiality by storing secrets in a controlled access database
|
|
46
|
-
- Deny replay attacks by rejecting one-time passwords that have been used by the client (this requires storing the most
|
|
47
|
-
recently authenticated timestamp, OTP, or hash of the OTP in your database, and rejecting the OTP when a match is
|
|
48
|
-
|
|
51
|
+
- Deny replay attacks by rejecting one-time passwords that have been used by the client (this requires storing the most
|
|
52
|
+
recently authenticated timestamp, OTP, or hash of the OTP in your database, and rejecting the OTP when a match is
|
|
53
|
+
seen)
|
|
54
|
+
- Throttle (rate limit) brute-force attacks against your application's login functionality (see RFC 4226, section 7.3)
|
|
49
55
|
- When implementing a "greenfield" application, consider supporting
|
|
50
56
|
`FIDO U2F <https://en.wikipedia.org/wiki/Universal_2nd_Factor>`_/`WebAuthn <https://www.w3.org/TR/webauthn/>`_ in
|
|
51
57
|
addition to HOTP/TOTP. U2F uses asymmetric cryptography to avoid using a shared secret design, which strengthens your
|
|
52
58
|
MFA solution against server-side attacks. Hardware U2F also sequesters the client secret in a dedicated single-purpose
|
|
53
59
|
device, which strengthens your clients against client-side attacks. And by automating scoping of credentials to
|
|
54
|
-
relying party IDs (application origin/domain names), U2F adds protection against phishing attacks. One implementation
|
|
55
|
-
FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP <https://github.com/pyauth/pywarp>`_.
|
|
60
|
+
relying party IDs (application origin/domain names), U2F adds protection against phishing attacks. One implementation
|
|
61
|
+
of FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP <https://github.com/pyauth/pywarp>`_.
|
|
56
62
|
|
|
57
63
|
We also recommend that implementers read the
|
|
58
|
-
`OWASP Authentication Cheat Sheet
|
|
64
|
+
`OWASP Authentication Cheat Sheet
|
|
65
|
+
<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_ and
|
|
59
66
|
`NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_ for a high level overview of
|
|
60
67
|
authentication best practices.
|
|
61
68
|
|
|
@@ -64,8 +71,10 @@ Quick overview of using One Time Passwords on your phone
|
|
|
64
71
|
|
|
65
72
|
* OTPs involve a shared secret, stored both on the phone and the server
|
|
66
73
|
* OTPs can be generated on a phone without internet connectivity
|
|
67
|
-
* OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured
|
|
68
|
-
|
|
74
|
+
* OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured
|
|
75
|
+
with a password)
|
|
76
|
+
* Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR
|
|
77
|
+
Code
|
|
69
78
|
|
|
70
79
|
Installation
|
|
71
80
|
------------
|
|
@@ -80,6 +89,9 @@ Time-based OTPs
|
|
|
80
89
|
~~~~~~~~~~~~~~~
|
|
81
90
|
::
|
|
82
91
|
|
|
92
|
+
import pyotp
|
|
93
|
+
import time
|
|
94
|
+
|
|
83
95
|
totp = pyotp.TOTP('base32secret3232')
|
|
84
96
|
totp.now() # => '492039'
|
|
85
97
|
|
|
@@ -92,6 +104,8 @@ Counter-based OTPs
|
|
|
92
104
|
~~~~~~~~~~~~~~~~~~
|
|
93
105
|
::
|
|
94
106
|
|
|
107
|
+
import pyotp
|
|
108
|
+
|
|
95
109
|
hotp = pyotp.HOTP('base32secret3232')
|
|
96
110
|
hotp.at(0) # => '260182'
|
|
97
111
|
hotp.at(1) # => '055283'
|
|
@@ -103,7 +117,8 @@ Counter-based OTPs
|
|
|
103
117
|
|
|
104
118
|
Generating a Secret Key
|
|
105
119
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
106
|
-
A helper function is provided to generate a 32-character base32 secret, compatible with Google Authenticator and other
|
|
120
|
+
A helper function is provided to generate a 32-character base32 secret, compatible with Google Authenticator and other
|
|
121
|
+
OTP apps::
|
|
107
122
|
|
|
108
123
|
pyotp.random_base32()
|
|
109
124
|
|
|
@@ -114,8 +129,8 @@ Some applications want the secret key to be formatted as a hex-encoded string::
|
|
|
114
129
|
Google Authenticator Compatible
|
|
115
130
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
116
131
|
|
|
117
|
-
PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes
|
|
118
|
-
ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
|
|
132
|
+
PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes
|
|
133
|
+
the ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
|
|
119
134
|
|
|
120
135
|
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name='alice@google.com', issuer_name='Secure App')
|
|
121
136
|
|
|
@@ -125,8 +140,8 @@ ability to generate provisioning URIs for use with the QR Code scanner built int
|
|
|
125
140
|
|
|
126
141
|
>>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
|
|
127
142
|
|
|
128
|
-
This URL can then be rendered as a QR Code (for example, using https://github.com/soldair/node-qrcode) which can then be
|
|
129
|
-
and added to the users list of OTP credentials.
|
|
143
|
+
This URL can then be rendered as a QR Code (for example, using https://github.com/soldair/node-qrcode) which can then be
|
|
144
|
+
scanned and added to the users list of OTP credentials.
|
|
130
145
|
|
|
131
146
|
Parsing these URLs is also supported::
|
|
132
147
|
|
|
@@ -151,6 +166,13 @@ Now run the following and compare the output::
|
|
|
151
166
|
totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
|
|
152
167
|
print("Current OTP:", totp.now())
|
|
153
168
|
|
|
169
|
+
Third-party contributions
|
|
170
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
171
|
+
The following third-party contributions are not described by a standard, not officially supported, and provided for
|
|
172
|
+
reference only:
|
|
173
|
+
|
|
174
|
+
* ``pyotp.contrib.Steam()``: An implementation of Steam TOTP. Uses the same API as `pyotp.TOTP()`.
|
|
175
|
+
|
|
154
176
|
Links
|
|
155
177
|
~~~~~
|
|
156
178
|
|
|
@@ -170,6 +192,13 @@ For new applications:
|
|
|
170
192
|
* `WebAuthn <https://www.w3.org/TR/webauthn/>`_
|
|
171
193
|
* `PyWARP <https://github.com/pyauth/pywarp>`_
|
|
172
194
|
|
|
195
|
+
Versioning
|
|
196
|
+
~~~~~~~~~~
|
|
197
|
+
This package follows the `Semantic Versioning 2.0.0 <http://semver.org/>`_ standard. To control changes, it is
|
|
198
|
+
recommended that application developers pin the package version and manage it using `pip-tools
|
|
199
|
+
<https://github.com/jazzband/pip-tools>`_ or similar. For library developers, pinning the major version is
|
|
200
|
+
recommended.
|
|
201
|
+
|
|
173
202
|
.. image:: https://github.com/pyauth/pyotp/workflows/Python%20package/badge.svg
|
|
174
203
|
:target: https://github.com/pyauth/pyotp/actions
|
|
175
204
|
.. image:: https://img.shields.io/codecov/c/github/pyauth/pyotp/master.svg
|
|
@@ -18,19 +18,21 @@ minimum, application implementers should follow this checklist:
|
|
|
18
18
|
|
|
19
19
|
- Ensure transport confidentiality by using HTTPS
|
|
20
20
|
- Ensure HOTP/TOTP secret confidentiality by storing secrets in a controlled access database
|
|
21
|
-
- Deny replay attacks by rejecting one-time passwords that have been used by the client (this requires storing the most
|
|
22
|
-
recently authenticated timestamp, OTP, or hash of the OTP in your database, and rejecting the OTP when a match is
|
|
23
|
-
|
|
21
|
+
- Deny replay attacks by rejecting one-time passwords that have been used by the client (this requires storing the most
|
|
22
|
+
recently authenticated timestamp, OTP, or hash of the OTP in your database, and rejecting the OTP when a match is
|
|
23
|
+
seen)
|
|
24
|
+
- Throttle (rate limit) brute-force attacks against your application's login functionality (see RFC 4226, section 7.3)
|
|
24
25
|
- When implementing a "greenfield" application, consider supporting
|
|
25
26
|
`FIDO U2F <https://en.wikipedia.org/wiki/Universal_2nd_Factor>`_/`WebAuthn <https://www.w3.org/TR/webauthn/>`_ in
|
|
26
27
|
addition to HOTP/TOTP. U2F uses asymmetric cryptography to avoid using a shared secret design, which strengthens your
|
|
27
28
|
MFA solution against server-side attacks. Hardware U2F also sequesters the client secret in a dedicated single-purpose
|
|
28
29
|
device, which strengthens your clients against client-side attacks. And by automating scoping of credentials to
|
|
29
|
-
relying party IDs (application origin/domain names), U2F adds protection against phishing attacks. One implementation
|
|
30
|
-
FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP <https://github.com/pyauth/pywarp>`_.
|
|
30
|
+
relying party IDs (application origin/domain names), U2F adds protection against phishing attacks. One implementation
|
|
31
|
+
of FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP <https://github.com/pyauth/pywarp>`_.
|
|
31
32
|
|
|
32
33
|
We also recommend that implementers read the
|
|
33
|
-
`OWASP Authentication Cheat Sheet
|
|
34
|
+
`OWASP Authentication Cheat Sheet
|
|
35
|
+
<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_ and
|
|
34
36
|
`NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_ for a high level overview of
|
|
35
37
|
authentication best practices.
|
|
36
38
|
|
|
@@ -39,8 +41,10 @@ Quick overview of using One Time Passwords on your phone
|
|
|
39
41
|
|
|
40
42
|
* OTPs involve a shared secret, stored both on the phone and the server
|
|
41
43
|
* OTPs can be generated on a phone without internet connectivity
|
|
42
|
-
* OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured
|
|
43
|
-
|
|
44
|
+
* OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured
|
|
45
|
+
with a password)
|
|
46
|
+
* Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR
|
|
47
|
+
Code
|
|
44
48
|
|
|
45
49
|
Installation
|
|
46
50
|
------------
|
|
@@ -55,6 +59,9 @@ Time-based OTPs
|
|
|
55
59
|
~~~~~~~~~~~~~~~
|
|
56
60
|
::
|
|
57
61
|
|
|
62
|
+
import pyotp
|
|
63
|
+
import time
|
|
64
|
+
|
|
58
65
|
totp = pyotp.TOTP('base32secret3232')
|
|
59
66
|
totp.now() # => '492039'
|
|
60
67
|
|
|
@@ -67,6 +74,8 @@ Counter-based OTPs
|
|
|
67
74
|
~~~~~~~~~~~~~~~~~~
|
|
68
75
|
::
|
|
69
76
|
|
|
77
|
+
import pyotp
|
|
78
|
+
|
|
70
79
|
hotp = pyotp.HOTP('base32secret3232')
|
|
71
80
|
hotp.at(0) # => '260182'
|
|
72
81
|
hotp.at(1) # => '055283'
|
|
@@ -78,7 +87,8 @@ Counter-based OTPs
|
|
|
78
87
|
|
|
79
88
|
Generating a Secret Key
|
|
80
89
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
81
|
-
A helper function is provided to generate a 32-character base32 secret, compatible with Google Authenticator and other
|
|
90
|
+
A helper function is provided to generate a 32-character base32 secret, compatible with Google Authenticator and other
|
|
91
|
+
OTP apps::
|
|
82
92
|
|
|
83
93
|
pyotp.random_base32()
|
|
84
94
|
|
|
@@ -89,8 +99,8 @@ Some applications want the secret key to be formatted as a hex-encoded string::
|
|
|
89
99
|
Google Authenticator Compatible
|
|
90
100
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
91
101
|
|
|
92
|
-
PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes
|
|
93
|
-
ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
|
|
102
|
+
PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes
|
|
103
|
+
the ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
|
|
94
104
|
|
|
95
105
|
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name='alice@google.com', issuer_name='Secure App')
|
|
96
106
|
|
|
@@ -100,8 +110,8 @@ ability to generate provisioning URIs for use with the QR Code scanner built int
|
|
|
100
110
|
|
|
101
111
|
>>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
|
|
102
112
|
|
|
103
|
-
This URL can then be rendered as a QR Code (for example, using https://github.com/soldair/node-qrcode) which can then be
|
|
104
|
-
and added to the users list of OTP credentials.
|
|
113
|
+
This URL can then be rendered as a QR Code (for example, using https://github.com/soldair/node-qrcode) which can then be
|
|
114
|
+
scanned and added to the users list of OTP credentials.
|
|
105
115
|
|
|
106
116
|
Parsing these URLs is also supported::
|
|
107
117
|
|
|
@@ -126,6 +136,13 @@ Now run the following and compare the output::
|
|
|
126
136
|
totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
|
|
127
137
|
print("Current OTP:", totp.now())
|
|
128
138
|
|
|
139
|
+
Third-party contributions
|
|
140
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
141
|
+
The following third-party contributions are not described by a standard, not officially supported, and provided for
|
|
142
|
+
reference only:
|
|
143
|
+
|
|
144
|
+
* ``pyotp.contrib.Steam()``: An implementation of Steam TOTP. Uses the same API as `pyotp.TOTP()`.
|
|
145
|
+
|
|
129
146
|
Links
|
|
130
147
|
~~~~~
|
|
131
148
|
|
|
@@ -145,6 +162,13 @@ For new applications:
|
|
|
145
162
|
* `WebAuthn <https://www.w3.org/TR/webauthn/>`_
|
|
146
163
|
* `PyWARP <https://github.com/pyauth/pywarp>`_
|
|
147
164
|
|
|
165
|
+
Versioning
|
|
166
|
+
~~~~~~~~~~
|
|
167
|
+
This package follows the `Semantic Versioning 2.0.0 <http://semver.org/>`_ standard. To control changes, it is
|
|
168
|
+
recommended that application developers pin the package version and manage it using `pip-tools
|
|
169
|
+
<https://github.com/jazzband/pip-tools>`_ or similar. For library developers, pinning the major version is
|
|
170
|
+
recommended.
|
|
171
|
+
|
|
148
172
|
.. image:: https://github.com/pyauth/pyotp/workflows/Python%20package/badge.svg
|
|
149
173
|
:target: https://github.com/pyauth/pyotp/actions
|
|
150
174
|
.. image:: https://img.shields.io/codecov/c/github/pyauth/pyotp/master.svg
|
pyotp-2.9.0/setup.cfg
ADDED
|
@@ -4,15 +4,24 @@ from setuptools import setup
|
|
|
4
4
|
|
|
5
5
|
setup(
|
|
6
6
|
name="pyotp",
|
|
7
|
-
version="2.
|
|
7
|
+
version="2.9.0",
|
|
8
8
|
url="https://github.com/pyotp/pyotp",
|
|
9
|
+
project_urls={
|
|
10
|
+
"Documentation": "https://pyauth.github.io/pyotp",
|
|
11
|
+
"Source Code": "https://github.com/pyauth/pyotp",
|
|
12
|
+
"Issue Tracker": "https://github.com/pyauth/pyotp/issues",
|
|
13
|
+
"Change Log": "https://github.com/pyauth/pyotp/blob/master/Changes.rst",
|
|
14
|
+
},
|
|
9
15
|
license="MIT License",
|
|
10
16
|
author="PyOTP contributors",
|
|
11
17
|
author_email="kislyuk@gmail.com",
|
|
12
18
|
description="Python One Time Password Library",
|
|
13
19
|
long_description=open("README.rst").read(),
|
|
14
|
-
python_requires=">=3.
|
|
20
|
+
python_requires=">=3.7",
|
|
15
21
|
install_requires=[],
|
|
22
|
+
extras_require={
|
|
23
|
+
"test": ["coverage", "wheel", "ruff", "mypy"],
|
|
24
|
+
},
|
|
16
25
|
packages=["pyotp", "pyotp.contrib"],
|
|
17
26
|
package_dir={"": "src"},
|
|
18
27
|
package_data={"pyotp": ["py.typed"]},
|
|
@@ -26,11 +35,11 @@ setup(
|
|
|
26
35
|
"Operating System :: POSIX",
|
|
27
36
|
"Programming Language :: Python",
|
|
28
37
|
"Programming Language :: Python :: 3",
|
|
29
|
-
"Programming Language :: Python :: 3.6",
|
|
30
38
|
"Programming Language :: Python :: 3.7",
|
|
31
39
|
"Programming Language :: Python :: 3.8",
|
|
32
40
|
"Programming Language :: Python :: 3.9",
|
|
33
41
|
"Programming Language :: Python :: 3.10",
|
|
42
|
+
"Programming Language :: Python :: 3.11",
|
|
34
43
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
35
44
|
],
|
|
36
45
|
)
|
|
@@ -40,8 +40,14 @@ def parse_uri(uri: str) -> OTP:
|
|
|
40
40
|
# Secret (to be filled in later)
|
|
41
41
|
secret = None
|
|
42
42
|
|
|
43
|
+
# Encoder (to be filled in later)
|
|
44
|
+
encoder = None
|
|
45
|
+
|
|
46
|
+
# Digits (to be filled in later)
|
|
47
|
+
digits = None
|
|
48
|
+
|
|
43
49
|
# Data we'll parse to the correct constructor
|
|
44
|
-
otp_data
|
|
50
|
+
otp_data: Dict[str, Any] = {}
|
|
45
51
|
|
|
46
52
|
# Parse with URLlib
|
|
47
53
|
parsed_uri = urlparse(unquote(uri))
|
|
@@ -74,10 +80,10 @@ def parse_uri(uri: str) -> OTP:
|
|
|
74
80
|
otp_data["digest"] = hashlib.sha512
|
|
75
81
|
else:
|
|
76
82
|
raise ValueError("Invalid value for algorithm, must be SHA1, SHA256 or SHA512")
|
|
83
|
+
elif key == "encoder":
|
|
84
|
+
encoder = value
|
|
77
85
|
elif key == "digits":
|
|
78
86
|
digits = int(value)
|
|
79
|
-
if digits not in [6, 7, 8]:
|
|
80
|
-
raise ValueError("Digits may only be 6, 7, or 8")
|
|
81
87
|
otp_data["digits"] = digits
|
|
82
88
|
elif key == "period":
|
|
83
89
|
otp_data["interval"] = int(value)
|
|
@@ -85,11 +91,17 @@ def parse_uri(uri: str) -> OTP:
|
|
|
85
91
|
otp_data["initial_count"] = int(value)
|
|
86
92
|
elif key != "image":
|
|
87
93
|
raise ValueError("{} is not a valid parameter".format(key))
|
|
88
|
-
|
|
94
|
+
|
|
95
|
+
if encoder != "steam":
|
|
96
|
+
if digits is not None and digits not in [6, 7, 8]:
|
|
97
|
+
raise ValueError("Digits may only be 6, 7, or 8")
|
|
98
|
+
|
|
89
99
|
if not secret:
|
|
90
100
|
raise ValueError("No secret found in URI")
|
|
91
101
|
|
|
92
102
|
# Create objects
|
|
103
|
+
if encoder == "steam":
|
|
104
|
+
return contrib.Steam(secret, **otp_data)
|
|
93
105
|
if parsed_uri.netloc == "totp":
|
|
94
106
|
return TOTP(secret, **otp_data)
|
|
95
107
|
elif parsed_uri.netloc == "hotp":
|
|
@@ -12,7 +12,14 @@ class Steam(TOTP):
|
|
|
12
12
|
Steam's custom TOTP. Subclass of `pyotp.totp.TOTP`.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
def __init__(
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
s: str,
|
|
18
|
+
name: Optional[str] = None,
|
|
19
|
+
issuer: Optional[str] = None,
|
|
20
|
+
interval: int = 30,
|
|
21
|
+
digits: int = 5
|
|
22
|
+
) -> None:
|
|
16
23
|
"""
|
|
17
24
|
:param s: secret in base32 format
|
|
18
25
|
:param interval: the time interval in seconds for OTP. This defaults to 30.
|
|
@@ -14,7 +14,7 @@ class HOTP(OTP):
|
|
|
14
14
|
self,
|
|
15
15
|
s: str,
|
|
16
16
|
digits: int = 6,
|
|
17
|
-
digest: Any =
|
|
17
|
+
digest: Any = None,
|
|
18
18
|
name: Optional[str] = None,
|
|
19
19
|
issuer: Optional[str] = None,
|
|
20
20
|
initial_count: int = 0,
|
|
@@ -23,10 +23,13 @@ class HOTP(OTP):
|
|
|
23
23
|
:param s: secret in base32 format
|
|
24
24
|
:param initial_count: starting HMAC counter value, defaults to 0
|
|
25
25
|
:param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more.
|
|
26
|
-
:param digest: digest function to use in the HMAC (expected to be
|
|
26
|
+
:param digest: digest function to use in the HMAC (expected to be SHA1)
|
|
27
27
|
:param name: account name
|
|
28
28
|
:param issuer: issuer
|
|
29
29
|
"""
|
|
30
|
+
if digest is None:
|
|
31
|
+
digest = hashlib.sha1
|
|
32
|
+
|
|
30
33
|
self.initial_count = initial_count
|
|
31
34
|
super().__init__(s=s, digits=digits, digest=digest, name=name, issuer=issuer)
|
|
32
35
|
|
|
@@ -18,6 +18,8 @@ class OTP(object):
|
|
|
18
18
|
issuer: Optional[str] = None,
|
|
19
19
|
) -> None:
|
|
20
20
|
self.digits = digits
|
|
21
|
+
if digits > 10:
|
|
22
|
+
raise ValueError("digits must be no greater than 10")
|
|
21
23
|
self.digest = digest
|
|
22
24
|
self.secret = s
|
|
23
25
|
self.name = name or "Secret"
|
|
@@ -39,11 +41,8 @@ class OTP(object):
|
|
|
39
41
|
| (hmac_hash[offset + 2] & 0xFF) << 8
|
|
40
42
|
| (hmac_hash[offset + 3] & 0xFF)
|
|
41
43
|
)
|
|
42
|
-
str_code = str(code % 10**self.digits)
|
|
43
|
-
|
|
44
|
-
str_code = "0" + str_code
|
|
45
|
-
|
|
46
|
-
return str_code
|
|
44
|
+
str_code = str(10_000_000_000 + (code % 10**self.digits))
|
|
45
|
+
return str_code[-self.digits :]
|
|
47
46
|
|
|
48
47
|
def byte_secret(self) -> bytes:
|
|
49
48
|
secret = self.secret
|
|
@@ -17,7 +17,7 @@ class TOTP(OTP):
|
|
|
17
17
|
self,
|
|
18
18
|
s: str,
|
|
19
19
|
digits: int = 6,
|
|
20
|
-
digest: Any =
|
|
20
|
+
digest: Any = None,
|
|
21
21
|
name: Optional[str] = None,
|
|
22
22
|
issuer: Optional[str] = None,
|
|
23
23
|
interval: int = 30,
|
|
@@ -26,10 +26,13 @@ class TOTP(OTP):
|
|
|
26
26
|
:param s: secret in base32 format
|
|
27
27
|
:param interval: the time interval in seconds for OTP. This defaults to 30.
|
|
28
28
|
:param digits: number of integers in the OTP. Some apps expect this to be 6 digits, others support more.
|
|
29
|
-
:param digest: digest function to use in the HMAC (expected to be
|
|
29
|
+
:param digest: digest function to use in the HMAC (expected to be SHA1)
|
|
30
30
|
:param name: account name
|
|
31
31
|
:param issuer: issuer
|
|
32
32
|
"""
|
|
33
|
+
if digest is None:
|
|
34
|
+
digest = hashlib.sha1
|
|
35
|
+
|
|
33
36
|
self.interval = interval
|
|
34
37
|
super().__init__(s=s, digits=digits, digest=digest, name=name, issuer=issuer)
|
|
35
38
|
|
|
@@ -37,7 +40,9 @@ class TOTP(OTP):
|
|
|
37
40
|
"""
|
|
38
41
|
Accepts either a Unix timestamp integer or a datetime object.
|
|
39
42
|
|
|
40
|
-
To get the time until the next timecode change (seconds until the current OTP expires), use this instead
|
|
43
|
+
To get the time until the next timecode change (seconds until the current OTP expires), use this instead:
|
|
44
|
+
|
|
45
|
+
.. code:: python
|
|
41
46
|
|
|
42
47
|
totp = pyotp.TOTP(...)
|
|
43
48
|
time_remaining = totp.interval - datetime.datetime.now().timestamp() % totp.interval
|
|
@@ -49,7 +49,7 @@ def build_uri(
|
|
|
49
49
|
otp_type = "hotp" if is_initial_count_present else "totp"
|
|
50
50
|
base_uri = "otpauth://{0}/{1}?{2}"
|
|
51
51
|
|
|
52
|
-
url_args
|
|
52
|
+
url_args: Dict[str, Union[None, int, str]] = {"secret": secret}
|
|
53
53
|
|
|
54
54
|
label = quote(name)
|
|
55
55
|
if issuer is not None:
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyotp
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.9.0
|
|
4
4
|
Summary: Python One Time Password Library
|
|
5
5
|
Home-page: https://github.com/pyotp/pyotp
|
|
6
6
|
Author: PyOTP contributors
|
|
7
7
|
Author-email: kislyuk@gmail.com
|
|
8
8
|
License: MIT License
|
|
9
|
+
Project-URL: Documentation, https://pyauth.github.io/pyotp
|
|
10
|
+
Project-URL: Source Code, https://github.com/pyauth/pyotp
|
|
11
|
+
Project-URL: Issue Tracker, https://github.com/pyauth/pyotp/issues
|
|
12
|
+
Project-URL: Change Log, https://github.com/pyauth/pyotp/blob/master/Changes.rst
|
|
9
13
|
Platform: MacOS X
|
|
10
14
|
Platform: Posix
|
|
11
15
|
Classifier: Intended Audience :: Developers
|
|
@@ -14,13 +18,14 @@ Classifier: Operating System :: MacOS :: MacOS X
|
|
|
14
18
|
Classifier: Operating System :: POSIX
|
|
15
19
|
Classifier: Programming Language :: Python
|
|
16
20
|
Classifier: Programming Language :: Python :: 3
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.6
|
|
18
21
|
Classifier: Programming Language :: Python :: 3.7
|
|
19
22
|
Classifier: Programming Language :: Python :: 3.8
|
|
20
23
|
Classifier: Programming Language :: Python :: 3.9
|
|
21
24
|
Classifier: Programming Language :: Python :: 3.10
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
26
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
-
Requires-Python: >=3.
|
|
27
|
+
Requires-Python: >=3.7
|
|
28
|
+
Provides-Extra: test
|
|
24
29
|
License-File: LICENSE
|
|
25
30
|
|
|
26
31
|
PyOTP - The Python One-Time Password Library
|
|
@@ -43,19 +48,21 @@ minimum, application implementers should follow this checklist:
|
|
|
43
48
|
|
|
44
49
|
- Ensure transport confidentiality by using HTTPS
|
|
45
50
|
- Ensure HOTP/TOTP secret confidentiality by storing secrets in a controlled access database
|
|
46
|
-
- Deny replay attacks by rejecting one-time passwords that have been used by the client (this requires storing the most
|
|
47
|
-
recently authenticated timestamp, OTP, or hash of the OTP in your database, and rejecting the OTP when a match is
|
|
48
|
-
|
|
51
|
+
- Deny replay attacks by rejecting one-time passwords that have been used by the client (this requires storing the most
|
|
52
|
+
recently authenticated timestamp, OTP, or hash of the OTP in your database, and rejecting the OTP when a match is
|
|
53
|
+
seen)
|
|
54
|
+
- Throttle (rate limit) brute-force attacks against your application's login functionality (see RFC 4226, section 7.3)
|
|
49
55
|
- When implementing a "greenfield" application, consider supporting
|
|
50
56
|
`FIDO U2F <https://en.wikipedia.org/wiki/Universal_2nd_Factor>`_/`WebAuthn <https://www.w3.org/TR/webauthn/>`_ in
|
|
51
57
|
addition to HOTP/TOTP. U2F uses asymmetric cryptography to avoid using a shared secret design, which strengthens your
|
|
52
58
|
MFA solution against server-side attacks. Hardware U2F also sequesters the client secret in a dedicated single-purpose
|
|
53
59
|
device, which strengthens your clients against client-side attacks. And by automating scoping of credentials to
|
|
54
|
-
relying party IDs (application origin/domain names), U2F adds protection against phishing attacks. One implementation
|
|
55
|
-
FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP <https://github.com/pyauth/pywarp>`_.
|
|
60
|
+
relying party IDs (application origin/domain names), U2F adds protection against phishing attacks. One implementation
|
|
61
|
+
of FIDO U2F/WebAuthn is PyOTP's sister project, `PyWARP <https://github.com/pyauth/pywarp>`_.
|
|
56
62
|
|
|
57
63
|
We also recommend that implementers read the
|
|
58
|
-
`OWASP Authentication Cheat Sheet
|
|
64
|
+
`OWASP Authentication Cheat Sheet
|
|
65
|
+
<https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_ and
|
|
59
66
|
`NIST SP 800-63-3: Digital Authentication Guideline <https://pages.nist.gov/800-63-3/>`_ for a high level overview of
|
|
60
67
|
authentication best practices.
|
|
61
68
|
|
|
@@ -64,8 +71,10 @@ Quick overview of using One Time Passwords on your phone
|
|
|
64
71
|
|
|
65
72
|
* OTPs involve a shared secret, stored both on the phone and the server
|
|
66
73
|
* OTPs can be generated on a phone without internet connectivity
|
|
67
|
-
* OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured
|
|
68
|
-
|
|
74
|
+
* OTPs should always be used as a second factor of authentication (if your phone is lost, you account is still secured
|
|
75
|
+
with a password)
|
|
76
|
+
* Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR
|
|
77
|
+
Code
|
|
69
78
|
|
|
70
79
|
Installation
|
|
71
80
|
------------
|
|
@@ -80,6 +89,9 @@ Time-based OTPs
|
|
|
80
89
|
~~~~~~~~~~~~~~~
|
|
81
90
|
::
|
|
82
91
|
|
|
92
|
+
import pyotp
|
|
93
|
+
import time
|
|
94
|
+
|
|
83
95
|
totp = pyotp.TOTP('base32secret3232')
|
|
84
96
|
totp.now() # => '492039'
|
|
85
97
|
|
|
@@ -92,6 +104,8 @@ Counter-based OTPs
|
|
|
92
104
|
~~~~~~~~~~~~~~~~~~
|
|
93
105
|
::
|
|
94
106
|
|
|
107
|
+
import pyotp
|
|
108
|
+
|
|
95
109
|
hotp = pyotp.HOTP('base32secret3232')
|
|
96
110
|
hotp.at(0) # => '260182'
|
|
97
111
|
hotp.at(1) # => '055283'
|
|
@@ -103,7 +117,8 @@ Counter-based OTPs
|
|
|
103
117
|
|
|
104
118
|
Generating a Secret Key
|
|
105
119
|
~~~~~~~~~~~~~~~~~~~~~~~
|
|
106
|
-
A helper function is provided to generate a 32-character base32 secret, compatible with Google Authenticator and other
|
|
120
|
+
A helper function is provided to generate a 32-character base32 secret, compatible with Google Authenticator and other
|
|
121
|
+
OTP apps::
|
|
107
122
|
|
|
108
123
|
pyotp.random_base32()
|
|
109
124
|
|
|
@@ -114,8 +129,8 @@ Some applications want the secret key to be formatted as a hex-encoded string::
|
|
|
114
129
|
Google Authenticator Compatible
|
|
115
130
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
116
131
|
|
|
117
|
-
PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes
|
|
118
|
-
ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
|
|
132
|
+
PyOTP works with the Google Authenticator iPhone and Android app, as well as other OTP apps like Authy. PyOTP includes
|
|
133
|
+
the ability to generate provisioning URIs for use with the QR Code scanner built into these MFA client apps::
|
|
119
134
|
|
|
120
135
|
pyotp.totp.TOTP('JBSWY3DPEHPK3PXP').provisioning_uri(name='alice@google.com', issuer_name='Secure App')
|
|
121
136
|
|
|
@@ -125,8 +140,8 @@ ability to generate provisioning URIs for use with the QR Code scanner built int
|
|
|
125
140
|
|
|
126
141
|
>>> 'otpauth://hotp/Secure%20App:alice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Secure%20App&counter=0'
|
|
127
142
|
|
|
128
|
-
This URL can then be rendered as a QR Code (for example, using https://github.com/soldair/node-qrcode) which can then be
|
|
129
|
-
and added to the users list of OTP credentials.
|
|
143
|
+
This URL can then be rendered as a QR Code (for example, using https://github.com/soldair/node-qrcode) which can then be
|
|
144
|
+
scanned and added to the users list of OTP credentials.
|
|
130
145
|
|
|
131
146
|
Parsing these URLs is also supported::
|
|
132
147
|
|
|
@@ -151,6 +166,13 @@ Now run the following and compare the output::
|
|
|
151
166
|
totp = pyotp.TOTP("JBSWY3DPEHPK3PXP")
|
|
152
167
|
print("Current OTP:", totp.now())
|
|
153
168
|
|
|
169
|
+
Third-party contributions
|
|
170
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
171
|
+
The following third-party contributions are not described by a standard, not officially supported, and provided for
|
|
172
|
+
reference only:
|
|
173
|
+
|
|
174
|
+
* ``pyotp.contrib.Steam()``: An implementation of Steam TOTP. Uses the same API as `pyotp.TOTP()`.
|
|
175
|
+
|
|
154
176
|
Links
|
|
155
177
|
~~~~~
|
|
156
178
|
|
|
@@ -170,6 +192,13 @@ For new applications:
|
|
|
170
192
|
* `WebAuthn <https://www.w3.org/TR/webauthn/>`_
|
|
171
193
|
* `PyWARP <https://github.com/pyauth/pywarp>`_
|
|
172
194
|
|
|
195
|
+
Versioning
|
|
196
|
+
~~~~~~~~~~
|
|
197
|
+
This package follows the `Semantic Versioning 2.0.0 <http://semver.org/>`_ standard. To control changes, it is
|
|
198
|
+
recommended that application developers pin the package version and manage it using `pip-tools
|
|
199
|
+
<https://github.com/jazzband/pip-tools>`_ or similar. For library developers, pinning the major version is
|
|
200
|
+
recommended.
|
|
201
|
+
|
|
173
202
|
.. image:: https://github.com/pyauth/pyotp/workflows/Python%20package/badge.svg
|
|
174
203
|
:target: https://github.com/pyauth/pyotp/actions
|
|
175
204
|
.. image:: https://img.shields.io/codecov/c/github/pyauth/pyotp/master.svg
|
|
@@ -2,7 +2,6 @@ LICENSE
|
|
|
2
2
|
MANIFEST.in
|
|
3
3
|
README.rst
|
|
4
4
|
pyproject.toml
|
|
5
|
-
setup.cfg
|
|
6
5
|
setup.py
|
|
7
6
|
test.py
|
|
8
7
|
src/pyotp/__init__.py
|
|
@@ -16,6 +15,7 @@ src/pyotp.egg-info/PKG-INFO
|
|
|
16
15
|
src/pyotp.egg-info/SOURCES.txt
|
|
17
16
|
src/pyotp.egg-info/dependency_links.txt
|
|
18
17
|
src/pyotp.egg-info/not-zip-safe
|
|
18
|
+
src/pyotp.egg-info/requires.txt
|
|
19
19
|
src/pyotp.egg-info/top_level.txt
|
|
20
20
|
src/pyotp/contrib/__init__.py
|
|
21
21
|
src/pyotp/contrib/steam.py
|
|
@@ -364,6 +364,13 @@ class ParseUriTest(unittest.TestCase):
|
|
|
364
364
|
with self.assertRaises(ValueError) as cm:
|
|
365
365
|
pyotp.parse_uri("otpauth://totp?algorithm=aes")
|
|
366
366
|
self.assertEqual("Invalid value for algorithm, must be SHA1, SHA256 or SHA512", str(cm.exception))
|
|
367
|
+
|
|
368
|
+
def test_parse_steam(self):
|
|
369
|
+
otp = pyotp.parse_uri("otpauth://totp/Steam:?secret=SOME_SECRET&encoder=steam")
|
|
370
|
+
self.assertEqual(type(otp), pyotp.contrib.Steam)
|
|
371
|
+
|
|
372
|
+
otp = pyotp.parse_uri("otpauth://totp/Steam:?secret=SOME_SECRET")
|
|
373
|
+
self.assertNotEqual(type(otp), pyotp.contrib.Steam)
|
|
367
374
|
|
|
368
375
|
@unittest.skipIf(sys.version_info < (3, 6), "Skipping test that requires deterministic dict key enumeration")
|
|
369
376
|
def test_algorithms(self):
|
|
@@ -420,6 +427,20 @@ class ParseUriTest(unittest.TestCase):
|
|
|
420
427
|
otp = pyotp.parse_uri(otp.provisioning_uri(name="n", issuer_name="i", image="https://test.net/test.png"))
|
|
421
428
|
self.assertEqual(hashlib.sha512, otp.digest)
|
|
422
429
|
|
|
430
|
+
otp = pyotp.parse_uri("otpauth://totp/Steam:?secret=FMXNK4QEGKVPULRTADY6JIDK5VHUBGZW&encoder=steam")
|
|
431
|
+
self.assertEqual(type(otp), pyotp.contrib.Steam)
|
|
432
|
+
self.assertEqual(otp.at(0), "C5V56")
|
|
433
|
+
self.assertEqual(otp.at(30), "QJY8Y")
|
|
434
|
+
self.assertEqual(otp.at(60), "R3WQY")
|
|
435
|
+
self.assertEqual(otp.at(90), "JG3T3")
|
|
436
|
+
|
|
437
|
+
# period and digits should be ignored
|
|
438
|
+
otp = pyotp.parse_uri("otpauth://totp/Steam:?secret=FMXNK4QEGKVPULRTADY6JIDK5VHUBGZW&period=15&digits=7&encoder=steam")
|
|
439
|
+
self.assertEqual(type(otp), pyotp.contrib.Steam)
|
|
440
|
+
self.assertEqual(otp.at(0), "C5V56")
|
|
441
|
+
self.assertEqual(otp.at(30), "QJY8Y")
|
|
442
|
+
self.assertEqual(otp.at(60), "R3WQY")
|
|
443
|
+
self.assertEqual(otp.at(90), "JG3T3")
|
|
423
444
|
|
|
424
445
|
class Timecop(object):
|
|
425
446
|
"""
|
pyotp-2.7.0/setup.cfg
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|