mirror of
https://github.com/scito/extract_otp_secret_keys.git
synced 2025-12-07 07:14:57 +01:00
refactor; update setup.py
more verbose logging better error messages
This commit is contained in:
parent
06b8efff62
commit
e754befb52
6 changed files with 108 additions and 77 deletions
17
README.md
17
README.md
|
|
@ -69,32 +69,33 @@ Known to work with
|
||||||
|
|
||||||
For protobuf versions 3.14.0 or similar or Python 3.6, use the extract_otp_secret_keys version 1.4.0.
|
For protobuf versions 3.14.0 or similar or Python 3.6, use the extract_otp_secret_keys version 1.4.0.
|
||||||
|
|
||||||
### Linux and macOS
|
### Shared libs installation for reading QR code images
|
||||||
|
|
||||||
For reading QR code images the zbar lib must be installed.
|
For reading QR code images the zbar library must be installed.
|
||||||
|
If you do not extract directly from images, you do not need to install the zbar shared library.
|
||||||
|
|
||||||
Detailed [installation documentation for pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar#installation).
|
For a detailed installation documentation of [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar#installation).
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
The zbar DLLs are included with the Windows Python wheels. On other operating systems, you will need to install the zbar shared library.
|
The zbar DLLs are included with the Windows Python wheels. On other operating systems, you will need to install the zbar shared library.
|
||||||
|
|
||||||
#### Mac OS X
|
|
||||||
|
|
||||||
brew install zbar
|
|
||||||
|
|
||||||
#### Linux (Debian, Ubuntu, ...)
|
#### Linux (Debian, Ubuntu, ...)
|
||||||
|
|
||||||
sudo apt-get install libzbar0
|
sudo apt-get install libzbar0
|
||||||
|
|
||||||
#### Linux (OpenSUSE)
|
#### Linux (OpenSUSE)
|
||||||
|
|
||||||
sudo zyper install libzbar0
|
sudo zypper install libzbar0
|
||||||
|
|
||||||
#### Linux (Fedora)
|
#### Linux (Fedora)
|
||||||
|
|
||||||
sudo dnf install libzbar0
|
sudo dnf install libzbar0
|
||||||
|
|
||||||
|
#### Mac OS X
|
||||||
|
|
||||||
|
brew install zbar
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Printing otp secrets form text file
|
### Printing otp secrets form text file
|
||||||
|
|
|
||||||
|
|
@ -100,8 +100,11 @@ def extract_otps(args):
|
||||||
|
|
||||||
otps = []
|
otps = []
|
||||||
|
|
||||||
i = j = 0
|
i = j = k = 0
|
||||||
|
if verbose: print('Input files: {}'.format(args.infile))
|
||||||
for infile in args.infile:
|
for infile in args.infile:
|
||||||
|
if verbose: print('Processing infile {}'.format(infile))
|
||||||
|
k += 1
|
||||||
for line in get_lines_from_file(infile):
|
for line in get_lines_from_file(infile):
|
||||||
if verbose: print(line)
|
if verbose: print(line)
|
||||||
if line.startswith('#') or line == '': continue
|
if line.startswith('#') or line == '': continue
|
||||||
|
|
@ -134,79 +137,83 @@ def extract_otps(args):
|
||||||
print()
|
print()
|
||||||
|
|
||||||
otps.append(otp)
|
otps.append(otp)
|
||||||
|
if verbose: print('{} infile(s) processed'.format(k))
|
||||||
return otps
|
return otps
|
||||||
|
|
||||||
|
|
||||||
def get_lines_from_file(filename):
|
def get_lines_from_file(filename):
|
||||||
lines = read_lines_from_text_file(filename)
|
# stdin stream cannot be rewinded, thus distinguish, use - for utf-8 stdin and = for binary image stdin
|
||||||
if are_bytes(lines):
|
if filename != '=':
|
||||||
abort('\nBinary input was given in stdin, please use = instead of -.')
|
check_file_exists(filename)
|
||||||
elif lines:
|
lines = read_lines_from_text_file(filename)
|
||||||
return lines
|
if lines:
|
||||||
|
return lines
|
||||||
|
|
||||||
# could not process text file, try reading as image
|
# could not process text file, try reading as image
|
||||||
return convert_img_to_line(filename)
|
if filename != '-':
|
||||||
|
return convert_img_to_line(filename)
|
||||||
|
|
||||||
|
|
||||||
def read_lines_from_text_file(filename):
|
def read_lines_from_text_file(filename):
|
||||||
if filename != '=':
|
if verbose: print('Reading lines of {}'.format(filename))
|
||||||
check_file_exists(filename)
|
finput = fileinput.input(filename)
|
||||||
finput = fileinput.input(filename)
|
try:
|
||||||
try:
|
lines = []
|
||||||
lines = []
|
for line in (line.strip() for line in finput):
|
||||||
for line in (line.strip() for line in finput):
|
if verbose: print(line)
|
||||||
# TODO improve
|
if is_binary(line):
|
||||||
# if verbose: print(line)
|
abort('\nBinary input was given in stdin, please use = instead of - as infile argument for images.')
|
||||||
# if line.startswith('#') or line == '':
|
# unfortunately yield line leads to random test fails
|
||||||
# continue
|
lines.append(line)
|
||||||
# unfortunately yield line leads to random test fails
|
return lines
|
||||||
lines.append(line)
|
except UnicodeDecodeError:
|
||||||
return lines
|
if filename == '-':
|
||||||
except UnicodeDecodeError:
|
abort('\nERROR: Unable to open text file form stdin. '
|
||||||
if filename == '-':
|
'In case you want read an image file from stdin, you must use "=" instead of "-".')
|
||||||
abort('\nERROR: Unable to open text file form stdin. '
|
else: # The file is probably an image, process below
|
||||||
'In case you want read an image file from stdin, you must use "=" instead of "-".')
|
return None
|
||||||
else: # The file is probably an image, process below
|
finally:
|
||||||
return None
|
finput.close()
|
||||||
finally:
|
|
||||||
finput.close()
|
|
||||||
|
|
||||||
|
|
||||||
def convert_img_to_line(filename):
|
def convert_img_to_line(filename):
|
||||||
if filename != '-':
|
if verbose: print('Reading image {}'.format(filename))
|
||||||
try:
|
try:
|
||||||
if filename != '=':
|
if filename != '=':
|
||||||
image = imread(filename)
|
image = imread(filename)
|
||||||
else:
|
else:
|
||||||
try:
|
|
||||||
stdin = sys.stdin.buffer.read()
|
|
||||||
except AttributeError:
|
|
||||||
# Workaround for pytest, since pytest cannot monkeypatch sys.stdin.buffer
|
|
||||||
stdin = sys.stdin.read()
|
|
||||||
try:
|
|
||||||
array = frombuffer(stdin, dtype='uint8')
|
|
||||||
except TypeError as e:
|
|
||||||
abort('\nERROR: Cannot read binary stdin buffer. Exception: {}'.format(str(e)))
|
|
||||||
image = imdecode(array, IMREAD_UNCHANGED)
|
|
||||||
|
|
||||||
if image is None:
|
|
||||||
abort('\nERROR: Unable to open file for reading.\ninput file: {}'.format(filename))
|
|
||||||
|
|
||||||
# dynamic import of QReader since this module has a dependency to zbar lib
|
|
||||||
try:
|
try:
|
||||||
from qreader import QReader
|
stdin = sys.stdin.buffer.read()
|
||||||
except ImportError as e:
|
except AttributeError:
|
||||||
abort('\nERROR: Cannot import QReader module probably due to missing zbar shared library. Exception:\n{}'.format(str(e)))
|
# Workaround for pytest, since pytest cannot monkeypatch sys.stdin.buffer
|
||||||
|
stdin = sys.stdin.read()
|
||||||
|
try:
|
||||||
|
array = frombuffer(stdin, dtype='uint8')
|
||||||
|
except TypeError as e:
|
||||||
|
abort('\nERROR: Cannot read binary stdin buffer. Exception: {}'.format(str(e)))
|
||||||
|
image = imdecode(array, IMREAD_UNCHANGED)
|
||||||
|
|
||||||
decoder = QReader()
|
if image is None:
|
||||||
decoded_text = decoder.detect_and_decode(image=image)
|
abort('\nERROR: Unable to open file for reading.\ninput file: {}'.format(filename))
|
||||||
if decoded_text is None:
|
|
||||||
abort('\nERROR: Unable to read QR Code from file.\ninput file: {}'.format(filename))
|
|
||||||
|
|
||||||
return [decoded_text]
|
# dynamic import of QReader since this module has a dependency to zbar lib and import it only when necessary
|
||||||
except Exception as e:
|
try:
|
||||||
abort('\nERROR: Encountered exception "{}".\ninput file: {}'.format(str(e), filename))
|
from qreader import QReader
|
||||||
|
except ImportError as e:
|
||||||
|
abort('''
|
||||||
|
ERROR: Cannot import QReader module. This problem is probably due to the missing zbar shared library.
|
||||||
|
On Linux and macOS libzbar0 must be installed.
|
||||||
|
See in README.md for the installation of the libzbar0.
|
||||||
|
Exception: {}'''.format(str(e)))
|
||||||
|
|
||||||
|
decoder = QReader()
|
||||||
|
decoded_text = decoder.detect_and_decode(image=image)
|
||||||
|
if decoded_text is None:
|
||||||
|
abort('\nERROR: Unable to read QR Code from file.\ninput file: {}'.format(filename))
|
||||||
|
|
||||||
|
return [decoded_text]
|
||||||
|
except Exception as e:
|
||||||
|
abort('\nERROR: Encountered exception "{}".\ninput file: {}'.format(str(e), filename))
|
||||||
|
|
||||||
|
|
||||||
def get_payload_from_line(line, i, infile):
|
def get_payload_from_line(line, i, infile):
|
||||||
|
|
@ -392,13 +399,12 @@ def check_file_exists(filename):
|
||||||
'\ninput file: {}'.format(filename))
|
'\ninput file: {}'.format(filename))
|
||||||
|
|
||||||
|
|
||||||
def are_bytes(lines):
|
def is_binary(line):
|
||||||
if lines and len(lines) > 0:
|
try:
|
||||||
try:
|
line.startswith('#')
|
||||||
lines[0].startswith('#')
|
return False
|
||||||
return False
|
except (UnicodeDecodeError, AttributeError, TypeError):
|
||||||
except (UnicodeDecodeError, AttributeError, TypeError):
|
return True
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def eprint(*args, **kwargs):
|
def eprint(*args, **kwargs):
|
||||||
|
|
|
||||||
4
setup.py
4
setup.py
|
|
@ -48,7 +48,9 @@ setup(
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'protobuf',
|
'protobuf',
|
||||||
'qrcode',
|
'qrcode',
|
||||||
'Pillow'
|
'Pillow',
|
||||||
|
'qreader',
|
||||||
|
'opencv-python'
|
||||||
],
|
],
|
||||||
|
|
||||||
project_urls={
|
project_urls={
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,24 @@
|
||||||
|
Input files: ['example_export.txt']
|
||||||
|
Processing infile example_export.txt
|
||||||
|
Reading lines of example_export.txt
|
||||||
|
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
|
||||||
|
otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D
|
||||||
|
|
||||||
|
# otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
# Name: "encoding: ¿äÄéÉ? (demo)"
|
||||||
|
otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D
|
||||||
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
|
# 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
|
||||||
# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
|
||||||
|
|
@ -136,3 +157,4 @@ Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
Type: totp
|
Type: totp
|
||||||
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
|
||||||
|
|
||||||
|
1 infile(s) processed
|
||||||
|
|
|
||||||
|
|
@ -562,7 +562,7 @@ def test_img_qr_reader_from_stdin_wrong_symbol(capsys, monkeypatch):
|
||||||
# Assert
|
# Assert
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
|
|
||||||
expected_stderr = '\nBinary input was given in stdin, please use = instead of -.\n'
|
expected_stderr = '\nBinary input was given in stdin, please use = instead of - as infile argument for images.\n'
|
||||||
|
|
||||||
assert captured.err == expected_stderr
|
assert captured.err == expected_stderr
|
||||||
assert captured.out == ''
|
assert captured.out == ''
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from utils import Capturing
|
||||||
|
|
||||||
import extract_otp_secret_keys
|
import extract_otp_secret_keys
|
||||||
|
|
||||||
class TestExtract(unittest.TestCase):
|
class TestQRImageExtract(unittest.TestCase):
|
||||||
def test_img_qr_reader_happy_path(self):
|
def test_img_qr_reader_happy_path(self):
|
||||||
with Capturing() as actual_output:
|
with Capturing() as actual_output:
|
||||||
extract_otp_secret_keys.main(['test/test_googleauth_export.png'])
|
extract_otp_secret_keys.main(['test/test_googleauth_export.png'])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue