diff options
| author | Simeon Simeonov | 2022-03-21 13:25:26 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2022-03-21 13:25:26 +0100 |
| commit | 12c5e38691b24d12a8fc9605e3b93fac86d461ae (patch) | |
| tree | e27a66b696bf42b61fe2c27e864e9a73c9b57d4a /otp2289/generator.py | |
| parent | 9179abf6c4b3e3eb2c991cbbd74738e160b910cc (diff) | |
Reformat the code with annotations and new code style
Diffstat (limited to 'otp2289/generator.py')
| -rw-r--r-- | otp2289/generator.py | 142 |
1 files changed, 82 insertions, 60 deletions
diff --git a/otp2289/generator.py b/otp2289/generator.py index 5ea738b..7b86e1b 100644 --- a/otp2289/generator.py +++ b/otp2289/generator.py | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
| 2 | # SPDX-License-Identifier: BSD-2-Clause-FreeBSD | 2 | # SPDX-License-Identifier: BSD-2-Clause-FreeBSD |
| 3 | # | 3 | # |
| 4 | # Copyright (c) 2020, Simeon Simeonov | 4 | # Copyright (c) 2020-2022 Simeon Simeonov |
| 5 | # All rights reserved. | 5 | # All rights reserved. |
| 6 | # | 6 | # |
| 7 | # Redistribution and use in source and binary forms, with or without | 7 | # Redistribution and use in source and binary forms, with or without |
| @@ -28,7 +28,6 @@ import binascii | |||
| 28 | import hashlib | 28 | import hashlib |
| 29 | import string | 29 | import string |
| 30 | 30 | ||
| 31 | |||
| 32 | OTP_ALGO_MD5 = 1 | 31 | OTP_ALGO_MD5 = 1 |
| 33 | OTP_ALGO_SHA1 = 2 | 32 | OTP_ALGO_SHA1 = 2 |
| 34 | 33 | ||
| @@ -306,7 +305,12 @@ class OTPChallengeException(Exception): | |||
| 306 | class OTPGenerator: | 305 | class OTPGenerator: |
| 307 | """OTPGenerator class""" | 306 | """OTPGenerator class""" |
| 308 | 307 | ||
| 309 | def __init__(self, password, seed='', hash_algo=OTP_ALGO_MD5): | 308 | def __init__( |
| 309 | self, | ||
| 310 | password: bytes, | ||
| 311 | seed: str = '', | ||
| 312 | hash_algo=OTP_ALGO_MD5, | ||
| 313 | ): | ||
| 310 | """ | 314 | """ |
| 311 | Constructs an OTPGenerator object with a given password and seed. | 315 | Constructs an OTPGenerator object with a given password and seed. |
| 312 | 316 | ||
| @@ -330,16 +334,19 @@ class OTPGenerator: | |||
| 330 | raise OTPGeneratorException('Password must be a byte-string') | 334 | raise OTPGeneratorException('Password must be a byte-string') |
| 331 | if len(password) < 10: | 335 | if len(password) < 10: |
| 332 | raise OTPGeneratorException( | 336 | raise OTPGeneratorException( |
| 333 | 'Password must be longer than 10 bytes') | 337 | 'Password must be longer than 10 bytes' |
| 338 | ) | ||
| 334 | self._password = password | 339 | self._password = password |
| 335 | 340 | ||
| 336 | def __repr__(self): | 341 | def __repr__(self): |
| 337 | """repr implementation""" | 342 | """repr implementation""" |
| 338 | return (f'{self.__class__} at {id(self)} (seed={self._seed}, ' | 343 | return ( |
| 339 | f'hash_algo={self._hash_algo})') | 344 | f'{self.__class__} at {id(self)} (seed={self._seed}, ' |
| 345 | f'hash_algo={self._hash_algo})' | ||
| 346 | ) | ||
| 340 | 347 | ||
| 341 | @staticmethod | 348 | @staticmethod |
| 342 | def bit_pair_sum(bit_stream): | 349 | def bit_pair_sum(bit_stream: str) -> int: |
| 343 | """ | 350 | """ |
| 344 | Split bit_stream in bit-pairs and sum them all together. | 351 | Split bit_stream in bit-pairs and sum them all together. |
| 345 | 352 | ||
| @@ -359,7 +366,7 @@ class OTPGenerator: | |||
| 359 | return value | 366 | return value |
| 360 | 367 | ||
| 361 | @staticmethod | 368 | @staticmethod |
| 362 | def bytes_to_tokens(hash_bytes): | 369 | def bytes_to_tokens(hash_bytes: bytes) -> str: |
| 363 | """ | 370 | """ |
| 364 | Returns a 6 words token from bytes as specified by RFC-2289. | 371 | Returns a 6 words token from bytes as specified by RFC-2289. |
| 365 | 372 | ||
| @@ -369,8 +376,7 @@ class OTPGenerator: | |||
| 369 | :return: 6 words tokens | 376 | :return: 6 words tokens |
| 370 | :rtype: str | 377 | :rtype: str |
| 371 | """ | 378 | """ |
| 372 | bit_stream = ''.join( | 379 | bit_stream = ''.join([f'{byte:0>8b}' for byte in hash_bytes]) |
| 373 | ['{0:0>8b}'.format(byte) for byte in hash_bytes]) | ||
| 374 | bit_pair_sum = OTPGenerator.bit_pair_sum(bit_stream) | 380 | bit_pair_sum = OTPGenerator.bit_pair_sum(bit_stream) |
| 375 | tokens = [] | 381 | tokens = [] |
| 376 | tokens.append(RFC1760_TOKENS[int(bit_stream[:11], 2)]) | 382 | tokens.append(RFC1760_TOKENS[int(bit_stream[:11], 2)]) |
| @@ -379,12 +385,17 @@ class OTPGenerator: | |||
| 379 | tokens.append(RFC1760_TOKENS[int(bit_stream[33:44], 2)]) | 385 | tokens.append(RFC1760_TOKENS[int(bit_stream[33:44], 2)]) |
| 380 | tokens.append(RFC1760_TOKENS[int(bit_stream[44:55], 2)]) | 386 | tokens.append(RFC1760_TOKENS[int(bit_stream[44:55], 2)]) |
| 381 | tokens.append( | 387 | tokens.append( |
| 382 | RFC1760_TOKENS[int( | 388 | RFC1760_TOKENS[ |
| 383 | bit_stream[55:64] + '{0:0>8b}'.format(bit_pair_sum)[-2:], 2)]) | 389 | int( |
| 390 | bit_stream[55:64] + f'{bit_pair_sum:0>8b}'[-2:], | ||
| 391 | 2, | ||
| 392 | ) | ||
| 393 | ] | ||
| 394 | ) | ||
| 384 | return ' '.join(tokens) | 395 | return ' '.join(tokens) |
| 385 | 396 | ||
| 386 | @staticmethod | 397 | @staticmethod |
| 387 | def get_tokens_from_challenge(challenge): | 398 | def get_tokens_from_challenge(challenge: str) -> tuple: |
| 388 | """ | 399 | """ |
| 389 | Returns tokens (seed, hash_algo and step) from a challenge string. | 400 | Returns tokens (seed, hash_algo and step) from a challenge string. |
| 390 | 401 | ||
| @@ -410,7 +421,7 @@ class OTPGenerator: | |||
| 410 | raise OTPChallengeException('Invalid challenge') from None | 421 | raise OTPChallengeException('Invalid challenge') from None |
| 411 | 422 | ||
| 412 | @staticmethod | 423 | @staticmethod |
| 413 | def sha1_digest_folding(sha1_digest): | 424 | def sha1_digest_folding(sha1_digest: bytes) -> bytes: |
| 414 | """ | 425 | """ |
| 415 | Implementation of the 160bit -> 64bit folding algorithm | 426 | Implementation of the 160bit -> 64bit folding algorithm |
| 416 | for sha1 digest. | 427 | for sha1 digest. |
| @@ -425,14 +436,17 @@ class OTPGenerator: | |||
| 425 | raise OTPGeneratorException('sha1_digest must be of type bytes') | 436 | raise OTPGeneratorException('sha1_digest must be of type bytes') |
| 426 | if len(sha1_digest) != 20: | 437 | if len(sha1_digest) != 20: |
| 427 | raise OTPGeneratorException( | 438 | raise OTPGeneratorException( |
| 428 | 'sha1_digest must be 160 bits (20 bytes) long') | 439 | 'sha1_digest must be 160 bits (20 bytes) long' |
| 440 | ) | ||
| 429 | digested = list(5 * b'i') # 5 bytes (40 bits) | 441 | digested = list(5 * b'i') # 5 bytes (40 bits) |
| 430 | result = list(8 * b'x') # 8 bytes (64 bits) | 442 | result = list(8 * b'x') # 8 bytes (64 bits) |
| 431 | for i in range(5): | 443 | for i in range(5): |
| 432 | digested[i] = (((sha1_digest[i * 4 + 0] & 0xff) << 24) | | 444 | digested[i] = ( |
| 433 | ((sha1_digest[i * 4 + 1] & 0xff) << 16) | | 445 | ((sha1_digest[i * 4 + 0] & 0xFF) << 24) |
| 434 | ((sha1_digest[i * 4 + 2] & 0xff) << 8) | | 446 | | ((sha1_digest[i * 4 + 1] & 0xFF) << 16) |
| 435 | (sha1_digest[i * 4 + 3] & 0xff)) | 447 | | ((sha1_digest[i * 4 + 2] & 0xFF) << 8) |
| 448 | | (sha1_digest[i * 4 + 3] & 0xFF) | ||
| 449 | ) | ||
| 436 | # sha.digest[0] ^= sha.digest[2]; | 450 | # sha.digest[0] ^= sha.digest[2]; |
| 437 | # sha.digest[1] ^= sha.digest[3]; | 451 | # sha.digest[1] ^= sha.digest[3]; |
| 438 | # sha.digest[0] ^= sha.digest[4]; | 452 | # sha.digest[0] ^= sha.digest[4]; |
| @@ -446,18 +460,18 @@ class OTPGenerator: | |||
| 446 | # result[j+3] = (unsigned char)((sha.digest[i] >> 24) & 0xff); | 460 | # result[j+3] = (unsigned char)((sha.digest[i] >> 24) & 0xff); |
| 447 | # } | 461 | # } |
| 448 | # just hardcoding the two iterations for better efficiency | 462 | # just hardcoding the two iterations for better efficiency |
| 449 | result[0] = digested[0] & 0xff | 463 | result[0] = digested[0] & 0xFF |
| 450 | result[1] = (digested[0] >> 8) & 0xff | 464 | result[1] = (digested[0] >> 8) & 0xFF |
| 451 | result[2] = (digested[0] >> 16) & 0xff | 465 | result[2] = (digested[0] >> 16) & 0xFF |
| 452 | result[3] = (digested[0] >> 24) & 0xff | 466 | result[3] = (digested[0] >> 24) & 0xFF |
| 453 | result[4] = digested[1] & 0xff | 467 | result[4] = digested[1] & 0xFF |
| 454 | result[5] = (digested[1] >> 8) & 0xff | 468 | result[5] = (digested[1] >> 8) & 0xFF |
| 455 | result[6] = (digested[1] >> 16) & 0xff | 469 | result[6] = (digested[1] >> 16) & 0xFF |
| 456 | result[7] = (digested[1] >> 24) & 0xff | 470 | result[7] = (digested[1] >> 24) & 0xFF |
| 457 | return bytes(result) | 471 | return bytes(result) |
| 458 | 472 | ||
| 459 | @staticmethod | 473 | @staticmethod |
| 460 | def strxor(byte_str1, byte_str2): | 474 | def strxor(byte_str1: bytes, byte_str2: bytes) -> bytes: |
| 461 | """ | 475 | """ |
| 462 | Implementation of strxor similar to the one provided by pycrypto. | 476 | Implementation of strxor similar to the one provided by pycrypto. |
| 463 | 477 | ||
| @@ -472,17 +486,17 @@ class OTPGenerator: | |||
| 472 | """ | 486 | """ |
| 473 | if not (isinstance(byte_str1, bytes) and isinstance(byte_str2, bytes)): | 487 | if not (isinstance(byte_str1, bytes) and isinstance(byte_str2, bytes)): |
| 474 | raise OTPGeneratorException( | 488 | raise OTPGeneratorException( |
| 475 | 'byte_str1 and byte_str2 must be of type bytes') | 489 | 'byte_str1 and byte_str2 must be of type bytes' |
| 490 | ) | ||
| 476 | length = len(byte_str1) | 491 | length = len(byte_str1) |
| 477 | if length != len(byte_str2) or length < 1: | 492 | if length != len(byte_str2) or length < 1: |
| 478 | raise OTPGeneratorException( | 493 | raise OTPGeneratorException( |
| 479 | 'byte_str1 and byte_str2 must be of the same length > 0') | 494 | 'byte_str1 and byte_str2 must be of the same length > 0' |
| 480 | return bytes( | 495 | ) |
| 481 | [byte_str1[i] ^ byte_str2[i] for i in range(length)] | 496 | return bytes([byte_str1[i] ^ byte_str2[i] for i in range(length)]) |
| 482 | ) | ||
| 483 | 497 | ||
| 484 | @staticmethod | 498 | @staticmethod |
| 485 | def tokens_to_bytes(tokens_str): | 499 | def tokens_to_bytes(tokens_str: str) -> bytes: |
| 486 | """ | 500 | """ |
| 487 | Returns bytes from a 6 words token as specified by RFC-2289. | 501 | Returns bytes from a 6 words token as specified by RFC-2289. |
| 488 | 502 | ||
| @@ -499,14 +513,17 @@ class OTPGenerator: | |||
| 499 | tokens = tokens_str.split() | 513 | tokens = tokens_str.split() |
| 500 | if len(tokens) != 6: | 514 | if len(tokens) != 6: |
| 501 | raise OTPGeneratorException( | 515 | raise OTPGeneratorException( |
| 502 | 'Tokens-string does not contain 6 tokens') | 516 | 'Tokens-string does not contain 6 tokens' |
| 517 | ) | ||
| 503 | token_ints = [] | 518 | token_ints = [] |
| 504 | try: | 519 | try: |
| 505 | token_ints = [RFC1760_TOKENS.index(token.upper()) for token in | 520 | token_ints = [ |
| 506 | tokens] | 521 | RFC1760_TOKENS.index(token.upper()) for token in tokens |
| 522 | ] | ||
| 507 | except ValueError: | 523 | except ValueError: |
| 508 | raise OTPGeneratorException( | 524 | raise OTPGeneratorException( |
| 509 | 'One or more words not present in RFC1760') from None | 525 | 'One or more words not present in RFC1760' |
| 526 | ) from None | ||
| 510 | # now we build a string of bits | 527 | # now we build a string of bits |
| 511 | bit_stream = format(token_ints[0], '011b') | 528 | bit_stream = format(token_ints[0], '011b') |
| 512 | bit_stream += format(token_ints[1], '011b') | 529 | bit_stream += format(token_ints[1], '011b') |
| @@ -519,14 +536,14 @@ class OTPGenerator: | |||
| 519 | # OTP servers MUST verify this checksum explicitly as part of the | 536 | # OTP servers MUST verify this checksum explicitly as part of the |
| 520 | # operation of decoding this representation of the one-time password. | 537 | # operation of decoding this representation of the one-time password. |
| 521 | if ( | 538 | if ( |
| 522 | '{0:0>8b}'.format(OTPGenerator.bit_pair_sum( | 539 | f'{OTPGenerator.bit_pair_sum(bit_stream[:64]):0>8b}'[-2:] |
| 523 | bit_stream[:64]))[-2:] != bit_stream[-2:] | 540 | != bit_stream[-2:] |
| 524 | ): | 541 | ): |
| 525 | raise OTPGeneratorException('Invalid bit checksum') | 542 | raise OTPGeneratorException('Invalid bit checksum') |
| 526 | return int(bit_stream[:64], 2).to_bytes(8, 'big') | 543 | return int(bit_stream[:64], 2).to_bytes(8, 'big') |
| 527 | 544 | ||
| 528 | @staticmethod | 545 | @staticmethod |
| 529 | def validate_hash_algo(hash_algo): | 546 | def validate_hash_algo(hash_algo) -> str: |
| 530 | """ | 547 | """ |
| 531 | Validates the provided hash-algorithm. | 548 | Validates the provided hash-algorithm. |
| 532 | 549 | ||
| @@ -541,19 +558,20 @@ class OTPGenerator: | |||
| 541 | if isinstance(hash_algo, int): | 558 | if isinstance(hash_algo, int): |
| 542 | if hash_algo not in _ALGO_DICT: | 559 | if hash_algo not in _ALGO_DICT: |
| 543 | raise OTPGeneratorException( | 560 | raise OTPGeneratorException( |
| 544 | 'hash_algo is not among the known algorithms') | 561 | 'hash_algo is not among the known algorithms' |
| 562 | ) | ||
| 545 | hash_algo = _ALGO_DICT.get(hash_algo) | 563 | hash_algo = _ALGO_DICT.get(hash_algo) |
| 546 | if not isinstance(hash_algo, str): | 564 | if not isinstance(hash_algo, str): |
| 547 | raise OTPGeneratorException( | 565 | raise OTPGeneratorException('hash_algo must be an int or a str') |
| 548 | 'hash_algo must be an int or a str') | ||
| 549 | if hash_algo not in hashlib.algorithms_available: | 566 | if hash_algo not in hashlib.algorithms_available: |
| 550 | raise OTPGeneratorException( | 567 | raise OTPGeneratorException( |
| 551 | f'{hash_algo} is not supported by this version of the ' | 568 | f'{hash_algo} is not supported by this version of the ' |
| 552 | 'hashlib module') | 569 | 'hashlib module' |
| 570 | ) | ||
| 553 | return hash_algo | 571 | return hash_algo |
| 554 | 572 | ||
| 555 | @staticmethod | 573 | @staticmethod |
| 556 | def validate_seed(seed): | 574 | def validate_seed(seed: str) -> str: |
| 557 | """ | 575 | """ |
| 558 | Validates the provided seed as defined by RFC-2289. | 576 | Validates the provided seed as defined by RFC-2289. |
| 559 | 577 | ||
| @@ -569,15 +587,17 @@ class OTPGenerator: | |||
| 569 | raise OTPGeneratorException('Seed must be a string') | 587 | raise OTPGeneratorException('Seed must be a string') |
| 570 | if not seed or len(seed) > 16: | 588 | if not seed or len(seed) > 16: |
| 571 | raise OTPGeneratorException( | 589 | raise OTPGeneratorException( |
| 572 | 'The seed MUST be of 1 to 16 characters in length') | 590 | 'The seed MUST be of 1 to 16 characters in length' |
| 591 | ) | ||
| 573 | for char in seed: | 592 | for char in seed: |
| 574 | if char not in string.ascii_letters + string.digits: | 593 | if char not in string.ascii_letters + string.digits: |
| 575 | raise OTPGeneratorException( | 594 | raise OTPGeneratorException( |
| 576 | 'The seed MUST consist of purely alphanumeric characters') | 595 | 'The seed MUST consist of purely alphanumeric characters' |
| 596 | ) | ||
| 577 | return seed | 597 | return seed |
| 578 | 598 | ||
| 579 | @staticmethod | 599 | @staticmethod |
| 580 | def validate_step(step): | 600 | def validate_step(step: int) -> int: |
| 581 | """ | 601 | """ |
| 582 | Validates the provided step as defined by RFC-2289. | 602 | Validates the provided step as defined by RFC-2289. |
| 583 | 603 | ||
| @@ -595,7 +615,7 @@ class OTPGenerator: | |||
| 595 | raise OTPGeneratorException('Step value MUST be >= 0') | 615 | raise OTPGeneratorException('Step value MUST be >= 0') |
| 596 | return step | 616 | return step |
| 597 | 617 | ||
| 598 | def generate_otp_hexdigest(self, step): | 618 | def generate_otp_hexdigest(self, step: int) -> str: |
| 599 | """ | 619 | """ |
| 600 | Generates the OTP hexdigest for the given step. | 620 | Generates the OTP hexdigest for the given step. |
| 601 | 621 | ||
| @@ -607,7 +627,7 @@ class OTPGenerator: | |||
| 607 | """ | 627 | """ |
| 608 | return '0x' + binascii.hexlify(self._generate_otp_bytes(step)).decode() | 628 | return '0x' + binascii.hexlify(self._generate_otp_bytes(step)).decode() |
| 609 | 629 | ||
| 610 | def generate_otp_hexdigest_from_challenge(self, challenge): | 630 | def generate_otp_hexdigest_from_challenge(self, challenge: str) -> str: |
| 611 | """ | 631 | """ |
| 612 | Same as generate_otp_hexdigest, but it generates hex. from a challenge. | 632 | Same as generate_otp_hexdigest, but it generates hex. from a challenge. |
| 613 | 633 | ||
| @@ -628,7 +648,7 @@ class OTPGenerator: | |||
| 628 | self._hash_algo = self.validate_hash_algo(hash_algo) | 648 | self._hash_algo = self.validate_hash_algo(hash_algo) |
| 629 | return self.generate_otp_hexdigest(step) | 649 | return self.generate_otp_hexdigest(step) |
| 630 | 650 | ||
| 631 | def generate_otp_words(self, step): | 651 | def generate_otp_words(self, step: int) -> str: |
| 632 | """ | 652 | """ |
| 633 | Generates the OTP six words token for the given step. | 653 | Generates the OTP six words token for the given step. |
| 634 | 654 | ||
| @@ -640,7 +660,7 @@ class OTPGenerator: | |||
| 640 | """ | 660 | """ |
| 641 | return self.bytes_to_tokens(self._generate_otp_bytes(step)) | 661 | return self.bytes_to_tokens(self._generate_otp_bytes(step)) |
| 642 | 662 | ||
| 643 | def generate_otp_words_from_challenge(self, challenge): | 663 | def generate_otp_words_from_challenge(self, challenge: str) -> str: |
| 644 | """ | 664 | """ |
| 645 | Same as generate_otp_words, but it generates words from a challenge. | 665 | Same as generate_otp_words, but it generates words from a challenge. |
| 646 | 666 | ||
| @@ -661,7 +681,7 @@ class OTPGenerator: | |||
| 661 | self._hash_algo = self.validate_hash_algo(hash_algo) | 681 | self._hash_algo = self.validate_hash_algo(hash_algo) |
| 662 | return self.generate_otp_words(step) | 682 | return self.generate_otp_words(step) |
| 663 | 683 | ||
| 664 | def hexdigest_range(self, start=499, stop=0): | 684 | def hexdigest_range(self, start: int = 499, stop: int = 0): |
| 665 | """ | 685 | """ |
| 666 | Returns an iterator that providing hexdigests corresponding to steps | 686 | Returns an iterator that providing hexdigests corresponding to steps |
| 667 | from `start` to and including `stop`. | 687 | from `start` to and including `stop`. |
| @@ -679,11 +699,12 @@ class OTPGenerator: | |||
| 679 | raise OTPGeneratorException('Step value MUST be an int') | 699 | raise OTPGeneratorException('Step value MUST be an int') |
| 680 | if start < stop: | 700 | if start < stop: |
| 681 | raise OTPGeneratorException( | 701 | raise OTPGeneratorException( |
| 682 | 'Start value can not be lower than stop') | 702 | 'Start value can not be lower than stop' |
| 703 | ) | ||
| 683 | for step in range(start, stop - 1, -1): | 704 | for step in range(start, stop - 1, -1): |
| 684 | yield self.generate_otp_hexdigest(step) | 705 | yield self.generate_otp_hexdigest(step) |
| 685 | 706 | ||
| 686 | def words_range(self, start=499, stop=0): | 707 | def words_range(self, start: int = 499, stop: int = 0): |
| 687 | """ | 708 | """ |
| 688 | Returns an iterator that providing the words corresponding to steps | 709 | Returns an iterator that providing the words corresponding to steps |
| 689 | from `start` to and including `stop`. | 710 | from `start` to and including `stop`. |
| @@ -701,11 +722,12 @@ class OTPGenerator: | |||
| 701 | raise OTPGeneratorException('Step value MUST be an int') | 722 | raise OTPGeneratorException('Step value MUST be an int') |
| 702 | if start < stop: | 723 | if start < stop: |
| 703 | raise OTPGeneratorException( | 724 | raise OTPGeneratorException( |
| 704 | 'Start value can not be lower than stop') | 725 | 'Start value can not be lower than stop' |
| 726 | ) | ||
| 705 | for step in range(start, stop - 1, -1): | 727 | for step in range(start, stop - 1, -1): |
| 706 | yield self.generate_otp_words(step) | 728 | yield self.generate_otp_words(step) |
| 707 | 729 | ||
| 708 | def _generate_otp_bytes(self, step): | 730 | def _generate_otp_bytes(self, step: int) -> bytes: |
| 709 | """ | 731 | """ |
| 710 | Generates the OTP bytes for the given step. | 732 | Generates the OTP bytes for the given step. |
| 711 | 733 | ||
| @@ -733,6 +755,6 @@ class OTPGenerator: | |||
| 733 | digest = self.sha1_digest_folding(large_digest) | 755 | digest = self.sha1_digest_folding(large_digest) |
| 734 | else: | 756 | else: |
| 735 | raise OTPGeneratorException( | 757 | raise OTPGeneratorException( |
| 736 | '{hash_algo} is not supported by this module'.format( | 758 | f'{self._hash_algo} is not supported by this module' |
| 737 | hash_algo=self._hash_algo)) | 759 | ) |
| 738 | return digest | 760 | return digest |
