From 595d5a458c5e534f5ab543c671f6109d6a42c600 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Thu, 1 Jul 2021 08:39:33 +0100 Subject: [PATCH] 2fa code: improve speed by reimplementing code gen code --- steamctl/commands/authenticator/__init__.py | 4 +- steamctl/commands/authenticator/cmd_code.py | 87 +++++++++++++++++++++ steamctl/commands/authenticator/cmds.py | 53 ------------- 3 files changed, 89 insertions(+), 55 deletions(-) create mode 100644 steamctl/commands/authenticator/cmd_code.py diff --git a/steamctl/commands/authenticator/__init__.py b/steamctl/commands/authenticator/__init__.py index 9e0597d..d9b5cda 100644 --- a/steamctl/commands/authenticator/__init__.py +++ b/steamctl/commands/authenticator/__init__.py @@ -52,7 +52,7 @@ def account_autocomplete(prefix, parsed_args, **kwargs): scp = sub_cp.add_parser("code", help="Generate auth code") scp.add_argument('account', type=str, help='Account name').completer = account_autocomplete - scp.set_defaults(_cmd_func=__name__ + '.cmds:cmd_authenticator_code') + scp.set_defaults(_cmd_func=__name__ + '.cmd_code:cmd_authenticator_code') scp = sub_cp.add_parser("qrcode", help="Generate QR code") scp.add_argument('--compat', action='store_true', @@ -64,5 +64,5 @@ def account_autocomplete(prefix, parsed_args, **kwargs): help='Invert QR code colors. Try if app fails to scan the code.' ) scp.add_argument('account', type=str, help='Account name').completer = account_autocomplete - scp.set_defaults(_cmd_func=__name__ + '.cmds:cmd_authenticator_qrcode') + scp.set_defaults(_cmd_func=__name__ + '.cmd_code:cmd_authenticator_qrcode') diff --git a/steamctl/commands/authenticator/cmd_code.py b/steamctl/commands/authenticator/cmd_code.py new file mode 100644 index 0000000..dbbe5b2 --- /dev/null +++ b/steamctl/commands/authenticator/cmd_code.py @@ -0,0 +1,87 @@ +import logging +import struct +from time import time +from base64 import b64decode, b32encode +from hashlib import sha1 +import hmac +from steamctl import __appname__ +from steamctl.utils.storage import UserDataFile + +_LOG = logging.getLogger(__name__) + +def generate_twofactor_code_for_time(shared_secret, timestamp): + """Generate Steam 2FA code for timestamp + + :param shared_secret: authenticator shared secret + :type shared_secret: bytes + :param timestamp: timestamp to use, if left out uses current time + :type timestamp: int + :return: steam two factor code + :rtype: str + """ + hashed = hmac.new(bytes(shared_secret), struct.pack('>Q', int(timestamp)//30), sha1).digest() + + start = ord(hashed[19:20]) & 0xF + codeint = struct.unpack('>I', hashed[start:start+4])[0] & 0x7fffffff + + charset = '23456789BCDFGHJKMNPQRTVWXY' + code = '' + + for _ in range(5): + codeint, i = divmod(codeint, len(charset)) + code += charset[i] + + return code + +def cmd_authenticator_code(args): + account = args.account.lower().strip() + secrets = UserDataFile('authenticator/{}.json'.format(account)).read_json() + + if not secrets: + print("No authenticator for %r" % account) + return 1 # error + + print(generate_twofactor_code_for_time(b64decode(secrets['shared_secret']), time())) + +def cmd_authenticator_qrcode(args): + account = args.account.lower().strip() + secrets = UserDataFile('authenticator/{}.json'.format(account)).read_json() + + if not secrets: + print("No authenticator for %r" % account) + return 1 # error + + import pyqrcode + + if args.invert: + FG, BG = '0', '1' + else: + FG, BG = '1', '0' + + charmap = { + (BG, BG): '█', + (FG, FG): ' ', + (BG, FG): '▀', + (FG, BG): '▄', + } + + if args.compat: + uri = 'otpauth://totp/steamctl:{user}?secret={secret}&issuer=Steam&digits=5' + else: + uri = 'otpauth://steam/steamctl:{user}?secret={secret}&issuer=Steam' + + + uri = uri.format(user=secrets['account_name'], + secret=b32encode(b64decode(secrets['shared_secret'])).decode('ascii'), + ) + + qrlines = pyqrcode.create(uri, error='M').text(1).split('\n')[:-1] + + print("Suggested 2FA App: Aegis, andOTP") + print("Scan the QR code below:") + + for y in range(0, len(qrlines), 2): + for x in range(0, len(qrlines[y])): + print(charmap[(qrlines[y][x], FG if y+1 >= len(qrlines) else qrlines[y+1][x])], end='') + print() + diff --git a/steamctl/commands/authenticator/cmds.py b/steamctl/commands/authenticator/cmds.py index bd2231a..f78b02b 100644 --- a/steamctl/commands/authenticator/cmds.py +++ b/steamctl/commands/authenticator/cmds.py @@ -346,56 +346,3 @@ def cmd_authenticator_status(args): print("Classified agent:", status['classified_agent']) print("Revocation attempts remaining:", status['revocation_attempts_remaining']) -def cmd_authenticator_code(args): - account = args.account.lower().strip() - secrets = UserDataFile('authenticator/{}.json'.format(account)).read_json() - - if not secrets: - print("No authenticator for %r" % account) - return 1 # error - - print(SteamAuthenticator(secrets).get_code()) - -def cmd_authenticator_qrcode(args): - account = args.account.lower().strip() - secrets = UserDataFile('authenticator/{}.json'.format(account)).read_json() - - if not secrets: - print("No authenticator for %r" % account) - return 1 # error - - import pyqrcode - from base64 import b64decode, b32encode - - if args.invert: - FG, BG = '0', '1' - else: - FG, BG = '1', '0' - - charmap = { - (BG, BG): '█', - (FG, FG): ' ', - (BG, FG): '▀', - (FG, BG): '▄', - } - - if args.compat: - uri = 'otpauth://totp/steamctl:{user}?secret={secret}&issuer=Steam&digits=5' - else: - uri = 'otpauth://steam/steamctl:{user}?secret={secret}&issuer=Steam' - - - uri = uri.format(user=secrets['account_name'], - secret=b32encode(b64decode(secrets['shared_secret'])).decode('ascii'), - ) - - qrlines = pyqrcode.create(uri, error='M').text(1).split('\n')[:-1] - - print("Suggested 2FA App: Aegis, andOTP") - print("Scan the QR code below:") - - for y in range(0, len(qrlines), 2): - for x in range(0, len(qrlines[y])): - print(charmap[(qrlines[y][x], FG if y+1 >= len(qrlines) else qrlines[y+1][x])], end='') - print() -