mirror of
https://github.com/scito/extract_otp_secret_keys.git
synced 2025-12-06 14:54:57 +01:00
refactor to reduce complexity of extract_otps (main loop)
flake8 is happy now
This commit is contained in:
parent
16d9fffc4a
commit
7fc69ea415
1 changed files with 99 additions and 73 deletions
|
|
@ -53,31 +53,19 @@ from re import compile as rcompile
|
||||||
import protobuf_generated_python.google_auth_pb2
|
import protobuf_generated_python.google_auth_pb2
|
||||||
|
|
||||||
|
|
||||||
# https://stackoverflow.com/questions/40226049/find-enums-listed-in-python-descriptor-for-protobuf
|
def sys_main():
|
||||||
def get_enum_name_by_number(parent, field_name):
|
main(sys.argv[1:])
|
||||||
field_value = getattr(parent, field_name)
|
|
||||||
return parent.DESCRIPTOR.fields_by_name[field_name].enum_type.values_by_number.get(field_value).name
|
|
||||||
|
|
||||||
|
|
||||||
def convert_secret_from_bytes_to_base32_str(bytes):
|
def main(sys_args):
|
||||||
return str(base64.b32encode(bytes), 'utf-8').replace('=', '')
|
global verbose, quiet
|
||||||
|
args = parse_args(sys_args)
|
||||||
|
verbose = args.verbose
|
||||||
|
quiet = args.quiet
|
||||||
|
|
||||||
|
otps = extract_otps(args)
|
||||||
def save_qr(args, data, name):
|
write_csv(args, otps)
|
||||||
from qrcode import QRCode
|
write_json(args, otps)
|
||||||
global verbose
|
|
||||||
qr = QRCode()
|
|
||||||
qr.add_data(data)
|
|
||||||
img = qr.make_image(fill_color='black', back_color='white')
|
|
||||||
if verbose: print('Saving to {}'.format(name))
|
|
||||||
img.save(name)
|
|
||||||
|
|
||||||
|
|
||||||
def print_qr(args, data):
|
|
||||||
from qrcode import QRCode
|
|
||||||
qr = QRCode()
|
|
||||||
qr.add_data(data)
|
|
||||||
qr.print_ascii()
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args(sys_args):
|
def parse_args(sys_args):
|
||||||
|
|
@ -96,21 +84,6 @@ def parse_args(sys_args):
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def sys_main():
|
|
||||||
main(sys.argv[1:])
|
|
||||||
|
|
||||||
|
|
||||||
def main(sys_args):
|
|
||||||
global verbose, quiet
|
|
||||||
args = parse_args(sys_args)
|
|
||||||
verbose = args.verbose
|
|
||||||
quiet = args.quiet
|
|
||||||
|
|
||||||
otps = extract_otps(args)
|
|
||||||
write_csv(args, otps)
|
|
||||||
write_json(args, otps)
|
|
||||||
|
|
||||||
|
|
||||||
def extract_otps(args):
|
def extract_otps(args):
|
||||||
global verbose, quiet
|
global verbose, quiet
|
||||||
quiet = args.quiet
|
quiet = args.quiet
|
||||||
|
|
@ -121,54 +94,107 @@ def extract_otps(args):
|
||||||
for line in (line.strip() for line in fileinput.input(args.infile)):
|
for line in (line.strip() for line in fileinput.input(args.infile)):
|
||||||
if verbose: print(line)
|
if verbose: print(line)
|
||||||
if line.startswith('#') or line == '': continue
|
if line.startswith('#') or line == '': continue
|
||||||
if not line.startswith('otpauth-migration://'): print('\nWARN: line is not a otpauth-migration:// URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
|
|
||||||
parsed_url = urlparse(line)
|
|
||||||
params = parse_qs(parsed_url.query)
|
|
||||||
if 'data' not in params:
|
|
||||||
print('\nERROR: no data query parameter in input URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
|
|
||||||
sys.exit(1)
|
|
||||||
data_encoded = params['data'][0]
|
|
||||||
data = base64.b64decode(data_encoded)
|
|
||||||
payload = protobuf_generated_python.google_auth_pb2.MigrationPayload()
|
|
||||||
payload.ParseFromString(data)
|
|
||||||
i += 1
|
i += 1
|
||||||
if verbose: print('\n{}. Payload Line'.format(i), payload, sep='\n')
|
payload = get_payload_from_line(line, i, args)
|
||||||
|
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
for otp in payload.otp_parameters:
|
for raw_otp in payload.otp_parameters:
|
||||||
j += 1
|
j += 1
|
||||||
if verbose: print('\n{}. Secret Key'.format(j))
|
if verbose: print('\n{}. Secret Key'.format(j))
|
||||||
if not quiet: print('Name: {}'.format(otp.name))
|
secret = convert_secret_from_bytes_to_base32_str(raw_otp.secret)
|
||||||
secret = convert_secret_from_bytes_to_base32_str(otp.secret)
|
otp_type = get_enum_name_by_number(raw_otp, 'type')
|
||||||
if not quiet: print('Secret: {}'.format(secret))
|
otp_url = build_otp_url(secret, raw_otp)
|
||||||
if otp.issuer and not quiet: print('Issuer: {}'.format(otp.issuer))
|
otp = {
|
||||||
otp_type = get_enum_name_by_number(otp, 'type')
|
"name": raw_otp.name,
|
||||||
if not quiet: print('Type: {}'.format(otp_type))
|
"secret": secret,
|
||||||
url_params = {'secret': secret}
|
"issuer": raw_otp.issuer,
|
||||||
if otp.type == 1: url_params['counter'] = otp.counter
|
"type": otp_type,
|
||||||
if otp.issuer: url_params['issuer'] = otp.issuer
|
"url": otp_url
|
||||||
otp_url = 'otpauth://{}/{}?'.format('totp' if otp.type == 2 else 'hotp', quote(otp.name)) + urlencode(url_params)
|
}
|
||||||
if verbose: print(otp_url)
|
if not quiet:
|
||||||
|
print_otp(otp)
|
||||||
if args.printqr:
|
if args.printqr:
|
||||||
print_qr(args, otp_url)
|
print_qr(args, otp_url)
|
||||||
if args.saveqr:
|
if args.saveqr:
|
||||||
if not (path.exists('qr')): mkdir('qr')
|
save_qr(otp, args, j)
|
||||||
pattern = rcompile(r'[\W_]+')
|
if not quiet:
|
||||||
file_otp_name = pattern.sub('', otp.name)
|
print()
|
||||||
file_otp_issuer = pattern.sub('', otp.issuer)
|
|
||||||
save_qr(args, otp_url, 'qr/{}-{}{}.png'.format(j, file_otp_name, '-' + file_otp_issuer if file_otp_issuer else ''))
|
|
||||||
if not quiet: print()
|
|
||||||
|
|
||||||
otps.append({
|
otps.append(otp)
|
||||||
"name": otp.name,
|
|
||||||
"secret": secret,
|
|
||||||
"issuer": otp.issuer,
|
|
||||||
"type": otp_type,
|
|
||||||
"url": otp_url
|
|
||||||
})
|
|
||||||
return otps
|
return otps
|
||||||
|
|
||||||
|
|
||||||
|
def get_payload_from_line(line, i, args):
|
||||||
|
if not line.startswith('otpauth-migration://'):
|
||||||
|
print('\nWARN: line is not a otpauth-migration:// URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
|
||||||
|
parsed_url = urlparse(line)
|
||||||
|
params = parse_qs(parsed_url.query)
|
||||||
|
if 'data' not in params:
|
||||||
|
print('\nERROR: no data query parameter in input URL\ninput file: {}\nline "{}"\nProbably a wrong file was given'.format(args.infile, line))
|
||||||
|
sys.exit(1)
|
||||||
|
data_encoded = params['data'][0]
|
||||||
|
data = base64.b64decode(data_encoded)
|
||||||
|
payload = protobuf_generated_python.google_auth_pb2.MigrationPayload()
|
||||||
|
payload.ParseFromString(data)
|
||||||
|
if verbose:
|
||||||
|
print('\n{}. Payload Line'.format(i), payload, sep='\n')
|
||||||
|
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
# https://stackoverflow.com/questions/40226049/find-enums-listed-in-python-descriptor-for-protobuf
|
||||||
|
def get_enum_name_by_number(parent, field_name):
|
||||||
|
field_value = getattr(parent, field_name)
|
||||||
|
return parent.DESCRIPTOR.fields_by_name[field_name].enum_type.values_by_number.get(field_value).name
|
||||||
|
|
||||||
|
|
||||||
|
def convert_secret_from_bytes_to_base32_str(bytes):
|
||||||
|
return str(base64.b32encode(bytes), 'utf-8').replace('=', '')
|
||||||
|
|
||||||
|
|
||||||
|
def build_otp_url(secret, raw_otp):
|
||||||
|
url_params = {'secret': secret}
|
||||||
|
if raw_otp.type == 1: url_params['counter'] = raw_otp.counter
|
||||||
|
if raw_otp.issuer: url_params['issuer'] = raw_otp.issuer
|
||||||
|
otp_url = 'otpauth://{}/{}?'.format('totp' if raw_otp.type == 2 else 'hotp', quote(raw_otp.name)) + urlencode(url_params)
|
||||||
|
return otp_url
|
||||||
|
|
||||||
|
|
||||||
|
def print_otp(otp):
|
||||||
|
print('Name: {}'.format(otp['name']))
|
||||||
|
print('Secret: {}'.format(otp['secret']))
|
||||||
|
if otp['issuer']: print('Issuer: {}'.format(otp['issuer']))
|
||||||
|
print('Type: {}'.format(otp['type']))
|
||||||
|
if verbose:
|
||||||
|
print(otp['url'])
|
||||||
|
|
||||||
|
|
||||||
|
def save_qr(otp, args, j):
|
||||||
|
if not (path.exists('qr')): mkdir('qr')
|
||||||
|
pattern = rcompile(r'[\W_]+')
|
||||||
|
file_otp_name = pattern.sub('', otp.name)
|
||||||
|
file_otp_issuer = pattern.sub('', otp.issuer)
|
||||||
|
save_qr_file(args, otp.url, 'qr/{}-{}{}.png'.format(j, file_otp_name, '-' + file_otp_issuer if file_otp_issuer else ''))
|
||||||
|
return file_otp_issuer
|
||||||
|
|
||||||
|
|
||||||
|
def save_qr_file(args, data, name):
|
||||||
|
from qrcode import QRCode
|
||||||
|
global verbose
|
||||||
|
qr = QRCode()
|
||||||
|
qr.add_data(data)
|
||||||
|
img = qr.make_image(fill_color='black', back_color='white')
|
||||||
|
if verbose: print('Saving to {}'.format(name))
|
||||||
|
img.save(name)
|
||||||
|
|
||||||
|
|
||||||
|
def print_qr(args, data):
|
||||||
|
from qrcode import QRCode
|
||||||
|
qr = QRCode()
|
||||||
|
qr.add_data(data)
|
||||||
|
qr.print_ascii()
|
||||||
|
|
||||||
|
|
||||||
def write_csv(args, otps):
|
def write_csv(args, otps):
|
||||||
global verbose, quiet
|
global verbose, quiet
|
||||||
if args.csv and len(otps) > 0:
|
if args.csv and len(otps) > 0:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue