From 174e4d3517aaf78a50f4bfea5327fd6ea9b17457 Mon Sep 17 00:00:00 2001 From: ncoop Date: Fri, 16 Jun 2017 10:42:39 -0700 Subject: [PATCH 1/5] Implement autopadding of secrets via optional param. --- onetimepass/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/onetimepass/__init__.py b/onetimepass/__init__.py index 7cbccf2..bb343e3 100755 --- a/onetimepass/__init__.py +++ b/onetimepass/__init__.py @@ -73,6 +73,7 @@ def get_hotp( intervals_no, as_string=False, casefold=True, + autopad=True, digest_method=hashlib.sha1, token_length=6, ): @@ -89,6 +90,8 @@ def get_hotp( :type as_string: bool :param casefold: True (default), if should accept also lowercase alphabet :type casefold: bool + :param autopad: True (default), if missing padding should be implied + :type autopad: bool :param digest_method: method of generating digest (hashlib.sha1 by default) :type digest_method: callable :param token_length: length of the token (6 by default) @@ -110,6 +113,9 @@ def get_hotp( # Get rid of all the spacing: secret = secret.replace(b' ', b'') try: + # Autopad only if the secret actually encodes a multiple of 8 bytes + if autopad and len(base64._bytes_from_decode_data(secret)) % 8: + secret += b'='* (8 - len(secret) % 8) key = base64.b32decode(secret, casefold=casefold) except (TypeError): raise TypeError('Incorrect secret') @@ -129,6 +135,7 @@ def get_hotp( def get_totp( secret, as_string=False, + autopad=True, digest_method=hashlib.sha1, token_length=6, interval_length=30, @@ -140,6 +147,8 @@ def get_totp( :type secret: str :param as_string: True if result should be padded string, False otherwise :type as_string: bool + :param autopad: True (default), if missing padding should be implied + :type autopad: bool :param digest_method: method of generating digest (hashlib.sha1 by default) :type digest_method: callable :param token_length: length of the token (6 by default) @@ -165,6 +174,7 @@ def get_totp( secret, intervals_no=interv_no, as_string=as_string, + autopad=autopad, digest_method=digest_method, token_length=token_length, ) From 1034fba64ba4f35d2453cf96a8511d242ae68283 Mon Sep 17 00:00:00 2001 From: ncoop Date: Fri, 16 Jun 2017 10:44:05 -0700 Subject: [PATCH 2/5] Test that autopadded and explicitly padded secrets work the same. --- tests/__init__.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/__init__.py b/tests/__init__.py index 58dfb2e..3043030 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -177,6 +177,17 @@ def test_hotp_for_range_preceding_match(self): secret = b'MFRGGZDFMZTWQ2LK' self.assertFalse(valid_hotp(713385, secret, last=1, trials=2)) + def test_autopadding(self): + """ + Check if unpadded and padded secrets resolve to the same token + """ + secret = b'MFRGGZDFMZTWQ2LK' + for length in [2, 4, 5, 7, 10, 12, 13, 15]: + fragment = secret[0:length] + fragment_padded = fragment + b'=' * (8 - len(fragment) % 8) + self.assertEqual(get_hotp(fragment_padded, 123, autopad=False), + get_hotp(fragment, 123, autopad=True)) + class TotpGenerationTestCase(TestCase): """ From b1e5d0ae2de72571244080fcfba82b6daa74b1ad Mon Sep 17 00:00:00 2001 From: ncoop Date: Fri, 16 Jun 2017 10:47:48 -0700 Subject: [PATCH 3/5] Remove autopad param: make it implied. --- onetimepass/__init__.py | 9 +-------- tests/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/onetimepass/__init__.py b/onetimepass/__init__.py index bb343e3..ff28ab2 100755 --- a/onetimepass/__init__.py +++ b/onetimepass/__init__.py @@ -73,7 +73,6 @@ def get_hotp( intervals_no, as_string=False, casefold=True, - autopad=True, digest_method=hashlib.sha1, token_length=6, ): @@ -90,8 +89,6 @@ def get_hotp( :type as_string: bool :param casefold: True (default), if should accept also lowercase alphabet :type casefold: bool - :param autopad: True (default), if missing padding should be implied - :type autopad: bool :param digest_method: method of generating digest (hashlib.sha1 by default) :type digest_method: callable :param token_length: length of the token (6 by default) @@ -114,7 +111,7 @@ def get_hotp( secret = secret.replace(b' ', b'') try: # Autopad only if the secret actually encodes a multiple of 8 bytes - if autopad and len(base64._bytes_from_decode_data(secret)) % 8: + if len(base64._bytes_from_decode_data(secret)) % 8: secret += b'='* (8 - len(secret) % 8) key = base64.b32decode(secret, casefold=casefold) except (TypeError): @@ -135,7 +132,6 @@ def get_hotp( def get_totp( secret, as_string=False, - autopad=True, digest_method=hashlib.sha1, token_length=6, interval_length=30, @@ -147,8 +143,6 @@ def get_totp( :type secret: str :param as_string: True if result should be padded string, False otherwise :type as_string: bool - :param autopad: True (default), if missing padding should be implied - :type autopad: bool :param digest_method: method of generating digest (hashlib.sha1 by default) :type digest_method: callable :param token_length: length of the token (6 by default) @@ -174,7 +168,6 @@ def get_totp( secret, intervals_no=interv_no, as_string=as_string, - autopad=autopad, digest_method=digest_method, token_length=token_length, ) diff --git a/tests/__init__.py b/tests/__init__.py index 3043030..77cbe35 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -185,8 +185,8 @@ def test_autopadding(self): for length in [2, 4, 5, 7, 10, 12, 13, 15]: fragment = secret[0:length] fragment_padded = fragment + b'=' * (8 - len(fragment) % 8) - self.assertEqual(get_hotp(fragment_padded, 123, autopad=False), - get_hotp(fragment, 123, autopad=True)) + self.assertEqual(get_hotp(fragment_padded, 123), + get_hotp(fragment, 123)) class TotpGenerationTestCase(TestCase): From f1c7be3a1fbbadee4c4e777baf8f5e4d1d0ab3e6 Mon Sep 17 00:00:00 2001 From: ncoop Date: Fri, 16 Jun 2017 11:56:16 -0700 Subject: [PATCH 4/5] Update README and version (1, 1, 0) --- README.rst | 2 ++ onetimepass/__init__.py | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index e423a3f..3cb6a9a 100644 --- a/README.rst +++ b/README.rst @@ -15,6 +15,8 @@ Changelog +---------+------------+------------------------------------------------------+ | Version | Date | Changes | +=========+============+======================================================+ +| 1.0.2 | 2017-06-16 | - automatically pad secrets to expected length | ++---------+------------+------------------------------------------------------+ | 1.0.1 | 2015-07-31 | - fixed tests and build system, | | | | - extended test coverage with Py3.5, PyPy and PyPy3, | +---------+------------+------------------------------------------------------+ diff --git a/onetimepass/__init__.py b/onetimepass/__init__.py index ff28ab2..5add0ac 100755 --- a/onetimepass/__init__.py +++ b/onetimepass/__init__.py @@ -3,7 +3,7 @@ time-based. It is compatible with Google Authenticator application and applications based on it. -@version: 1.0.1 +@version: 1.0.2 @author: Tomasz Jaskowski @contact: http://github.com/tadeck @license: MIT @@ -35,8 +35,8 @@ import time __author__ = 'Tomasz Jaskowski ' -__date__ = '31 July 2015' -__version_info__ = (1, 0, 1) +__date__ = '16 June 2017' +__version_info__ = (1, 1, 0) __version__ = '%s.%s.%s' % __version_info__ __license__ = 'MIT' From f0784ef3ed34ce8e61fba3b0ecb1303c9d4fa617 Mon Sep 17 00:00:00 2001 From: ncoop Date: Fri, 16 Jun 2017 16:03:25 -0700 Subject: [PATCH 5/5] Refrain from calling `base64._bytes_from_decode_data`. --- onetimepass/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/onetimepass/__init__.py b/onetimepass/__init__.py index 5add0ac..95fe19a 100755 --- a/onetimepass/__init__.py +++ b/onetimepass/__init__.py @@ -110,9 +110,10 @@ def get_hotp( # Get rid of all the spacing: secret = secret.replace(b' ', b'') try: - # Autopad only if the secret actually encodes a multiple of 8 bytes - if len(base64._bytes_from_decode_data(secret)) % 8: - secret += b'='* (8 - len(secret) % 8) + # Autopad if necessary + mod = len(secret) % 8 + if mod: + secret += b'=' * (8 - mod) key = base64.b32decode(secret, casefold=casefold) except (TypeError): raise TypeError('Incorrect secret')