mirror of
https://github.com/scito/extract_otp_secrets.git
synced 2025-12-13 10:20:10 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1377f032b9 | ||
|
|
a7a6247f47 | ||
|
|
e43d9ae925 | ||
|
|
6f884b4d05 | ||
|
|
b354c3d80d | ||
|
|
fb66c5f568 | ||
|
|
cc26f7b589 | ||
|
|
6068eb142c |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
qr/
|
||||
11
README.md
11
README.md
@@ -9,19 +9,24 @@ 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
|
||||
|
||||
pip install qrcode
|
||||
pip install qrcode[pil]
|
||||
|
||||
## Technical background
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -45,19 +45,20 @@ import argparse
|
||||
import base64
|
||||
import fileinput
|
||||
import sys
|
||||
from urllib.parse import parse_qs, urlencode, urlparse
|
||||
|
||||
from urllib.parse import parse_qs, urlencode, urlparse, quote
|
||||
from os import path, mkdir
|
||||
from re import sub, compile as rcompile
|
||||
import generated_python.google_auth_pb2
|
||||
|
||||
arg_parser = argparse.ArgumentParser()
|
||||
arg_parser.add_argument('--verbose', '-v', help='verbose output', action='store_true')
|
||||
arg_parser.add_argument('--qr', '-q', help='print QR codes (otpauth://...)', action='store_true')
|
||||
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to the "qr" subfolder', action='store_true')
|
||||
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal', action='store_true')
|
||||
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
|
||||
if args.qr:
|
||||
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):
|
||||
@@ -67,26 +68,41 @@ def get_enum_name_by_number(parent, field_name):
|
||||
def convert_secret_from_bytes_to_base32_str(bytes):
|
||||
return str(base64.b32encode(otp.secret), 'utf-8').replace('=', '')
|
||||
|
||||
def save_qr(data, name):
|
||||
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(data):
|
||||
qr = QRCode()
|
||||
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
|
||||
for otp in payload.otp_parameters:
|
||||
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))
|
||||
@@ -94,7 +110,13 @@ for line in (line.strip() for line in fileinput.input(args.infile)):
|
||||
url_params = { 'secret': secret }
|
||||
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', otp.name) + urlencode(url_params)
|
||||
if args.qr:
|
||||
if verbose: print(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 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)
|
||||
save_qr(otp_url, 'qr/{}-{}{}.png'.format(j, file_otp_name, '-' + file_otp_issuer if file_otp_issuer else ''))
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user