Compare commits

...

5 Commits

4 changed files with 35 additions and 22 deletions

View File

@@ -9,14 +9,19 @@ Extract two-factor authentication (2FA, TFA) secret keys from export QR codes of
3. Save the captured QR codes in a text file. Save each QR code on a new line. (The captured QR codes look like `otpauth-migration://offline?data=...`)
4. Call this script with the file as input:
python extract_otp_secret_keys.py -q example_export.txt
python extract_otp_secret_keys.py -p example_export.txt
## Requirement
The protobuf package of Google for proto3 is required for running this script.
The protobuf package of Google for proto3 is required for running this script. protobuf >= 3.14 is recommended.
pip install protobuf
Known to work with
* Python 3.6.12 and protobuf 3.14.0
* Python 3.8.5 and protobuf 3.14.0
### Optional
For printing QR codes, the qrcode module is required

View File

@@ -2,3 +2,10 @@
# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B

View File

@@ -2,10 +2,10 @@
#
# Usage:
# 1. Export the QR codes from "Google Authenticator" app
# 2. Read QR codes with QR code reader
# 2. Read QR codes with QR code reader (e.g. with a second device)
# 3. Save the captured QR codes in a text file. Save each QR code on a new line. (The captured QR codes look like "otpauth-migration://offline?data=...")
# 4. Call this script with the file as input:
# python extract_otp_secret_keys.py -q example_export.txt
# python extract_otp_secret_keys.py -p example_export.txt
#
# Requirement:
# The protobuf package of Google for proto3 is required for running this script.
@@ -57,12 +57,8 @@ arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the
arg_parser.add_argument('infile', help='file or - for stdin (default: -) with "otpauth-migration://..." URLs separated by newlines, lines starting with # are ignored')
args = arg_parser.parse_args()
if args.saveqr or args.printqr: from qrcode import QRCode
verbose = args.verbose
saveqr = args.saveqr
printqr = args.printqr
if saveqr or printqr:
from qrcode import QRCode
# https://stackoverflow.com/questions/40226049/find-enums-listed-in-python-descriptor-for-protobuf
def get_enum_name_by_number(parent, field_name):
@@ -84,22 +80,29 @@ def print_qr(data):
qr.add_data(data)
qr.print_tty()
i = j = 0
for line in (line.strip() for line in fileinput.input(args.infile)):
if verbose: print(line)
if line.startswith('#'): 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 not 'data' 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 = generated_python.google_auth_pb2.MigrationPayload()
payload.ParseFromString(data)
if verbose: print(payload)
i += 1
if verbose: print('\n{}. Payload Line'.format(i), payload, sep='\n')
# pylint: disable=no-member
i = 0
for otp in payload.otp_parameters:
i += 1
print('\nName: {}'.format(otp.name))
j += 1
if verbose: print('\n{}. Secret Key'.format(j))
else: print()
print('Name: {}'.format(otp.name))
secret = convert_secret_from_bytes_to_base32_str(otp.secret)
print('Secret: {}'.format(secret))
if otp.issuer: print('Issuer: {}'.format(otp.issuer))
@@ -108,14 +111,12 @@ for line in (line.strip() for line in fileinput.input(args.infile)):
if otp.type == 1: url_params['counter'] = otp.counter
if otp.issuer: url_params['issuer'] = otp.issuer
otp_url = 'otpauth://{}/{}?'.format('totp' if otp.type == 2 else 'hotp', quote(otp.name)) + urlencode(url_params)
if saveqr:
if verbose: print(otp_url)
if verbose: print(otp_url)
if args.printqr:
print_qr(otp_url)
if args.saveqr:
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)
if not(file_otp_issuer): print_qr(otp_url)
if file_otp_issuer: save_qr(otp_url, 'qr/{}-{}-{}.png'.format(i, file_otp_name, file_otp_issuer))
if printqr:
if verbose: print(otp_url)
print_qr(otp_url)
save_qr(otp_url, 'qr/{}-{}{}.png'.format(j, file_otp_name, '-' + file_otp_issuer if file_otp_issuer else ''))

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google_auth.proto
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import message as _message
from google.protobuf import reflection as _reflection