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.
@@ -1,11 +1,15 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyotp
3
- Version: 2.7.0
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.6
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 seen)
48
- - Throttle (rate limit) brute-force attacks against your application's login functionality
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 of
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 <https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_ and
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 with a password)
68
- * Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR Code
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 OTP apps::
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 the
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 scanned
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 seen)
23
- - Throttle (rate limit) brute-force attacks against your application's login functionality
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 of
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 <https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_ and
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 with a password)
43
- * Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR Code
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 OTP apps::
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 the
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 scanned
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
@@ -1,5 +1,9 @@
1
1
  [tool.black]
2
2
  line-length = 120
3
+
3
4
  [tool.isort]
4
5
  profile = "black"
5
6
  line_length = 120
7
+
8
+ [tool.ruff]
9
+ line-length = 120
pyotp-2.9.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -4,15 +4,24 @@ from setuptools import setup
4
4
 
5
5
  setup(
6
6
  name="pyotp",
7
- version="2.7.0",
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.6",
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 = {} # type: Dict[str, Any]
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__(self, s: str, name: Optional[str] = None, issuer: Optional[str] = None, interval: int = 30) -> None:
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 = hashlib.sha1,
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 sha1)
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
- while len(str_code) < self.digits:
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 = hashlib.sha1,
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 sha1)
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 = {"secret": secret} # type: Dict[str, Union[None, int, str]]
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.7.0
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.6
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 seen)
48
- - Throttle (rate limit) brute-force attacks against your application's login functionality
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 of
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 <https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Authentication_Cheat_Sheet.md>`_ and
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 with a password)
68
- * Google Authenticator and other OTP client apps allow you to store multiple OTP secrets and provision those using a QR Code
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 OTP apps::
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 the
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 scanned
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
@@ -0,0 +1,6 @@
1
+
2
+ [test]
3
+ coverage
4
+ wheel
5
+ ruff
6
+ mypy
@@ -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
@@ -1,8 +0,0 @@
1
- [flake8]
2
- max-line-length = 120
3
- extend-ignore = E203
4
-
5
- [egg_info]
6
- tag_build =
7
- tag_date = 0
8
-
File without changes
File without changes
File without changes
File without changes