mirror of
https://github.com/scito/extract_otp_secret_keys.git
synced 2025-12-06 06:44:57 +01:00
write txt file in cv2 and cmd; upgrade protobuf 4.22.0
This commit is contained in:
parent
5555dda9c2
commit
6bf02d045c
6 changed files with 149 additions and 81 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -29,3 +29,4 @@ extract_otp_secrets_linux_arm64.spec
|
||||||
extract_otp_secrets_linux_x86_64_bullseye.spec
|
extract_otp_secrets_linux_x86_64_bullseye.spec
|
||||||
extract_otp_secrets_linux_x86_64.spec
|
extract_otp_secrets_linux_x86_64.spec
|
||||||
extract_otp_secrets.spec
|
extract_otp_secrets.spec
|
||||||
|
test.txt
|
||||||
|
|
|
||||||
88
Pipfile.lock
generated
88
Pipfile.lock
generated
|
|
@ -176,23 +176,22 @@
|
||||||
},
|
},
|
||||||
"protobuf": {
|
"protobuf": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30",
|
"sha256:1669cb7524221a8e2d9008d0842453dbefdd0fcdd64d67672f657244867635fb",
|
||||||
"sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b",
|
"sha256:29288813aacaa302afa2381db1d6e0482165737b0afdf2811df5fa99185c457b",
|
||||||
"sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc",
|
"sha256:47d31bdf58222dd296976aa1646c68c6ee80b96d22e0a3c336c9174e253fd35e",
|
||||||
"sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791",
|
"sha256:652d8dfece122a24d98eebfef30e31e455d300efa41999d1182e015984ac5930",
|
||||||
"sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717",
|
"sha256:7c535d126e7dcc714105ab20b418c4fedbd28f8b8afc42b7350b1e317bbbcc71",
|
||||||
"sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec",
|
"sha256:86c3d20428b007537ba6792b475c0853bba7f66b1f60e610d913b77d94b486e4",
|
||||||
"sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7",
|
"sha256:a33a273d21852f911b8bda47f39f4383fe7c061eb1814db2c76c9875c89c2491",
|
||||||
"sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab",
|
"sha256:ab4d043865dd04e6b09386981fe8f80b39a1e46139fb4a3c206229d6b9f36ff6",
|
||||||
"sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2",
|
"sha256:b2fea9dc8e3c0f32c38124790ef16cba2ee0628fe2022a52e435e1117bfef9b1",
|
||||||
"sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5",
|
"sha256:c27f371f0159feb70e6ea52ed7e768b3f3a4c5676c1900a7e51a24740381650e",
|
||||||
"sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1",
|
"sha256:c3325803095fb4c2a48649c321d2fbde59f8fbfcb9bfc7a86df27d112831c571",
|
||||||
"sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462",
|
"sha256:e474b63bab0a2ea32a7b26a4d8eec59e33e709321e5e16fb66e766b61b82a95e",
|
||||||
"sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97",
|
"sha256:e894e9ae603e963f0842498c4cd5d39c6a60f0d7e4c103df50ee939564298658"
|
||||||
"sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.21.12"
|
"version": "==4.22.0"
|
||||||
},
|
},
|
||||||
"pypng": {
|
"pypng": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -227,11 +226,11 @@
|
||||||
},
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
|
"sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
|
||||||
"sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
|
"sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==4.4.0"
|
"version": "==4.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
|
|
@ -483,23 +482,22 @@
|
||||||
},
|
},
|
||||||
"protobuf": {
|
"protobuf": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30",
|
"sha256:1669cb7524221a8e2d9008d0842453dbefdd0fcdd64d67672f657244867635fb",
|
||||||
"sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b",
|
"sha256:29288813aacaa302afa2381db1d6e0482165737b0afdf2811df5fa99185c457b",
|
||||||
"sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc",
|
"sha256:47d31bdf58222dd296976aa1646c68c6ee80b96d22e0a3c336c9174e253fd35e",
|
||||||
"sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791",
|
"sha256:652d8dfece122a24d98eebfef30e31e455d300efa41999d1182e015984ac5930",
|
||||||
"sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717",
|
"sha256:7c535d126e7dcc714105ab20b418c4fedbd28f8b8afc42b7350b1e317bbbcc71",
|
||||||
"sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec",
|
"sha256:86c3d20428b007537ba6792b475c0853bba7f66b1f60e610d913b77d94b486e4",
|
||||||
"sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7",
|
"sha256:a33a273d21852f911b8bda47f39f4383fe7c061eb1814db2c76c9875c89c2491",
|
||||||
"sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab",
|
"sha256:ab4d043865dd04e6b09386981fe8f80b39a1e46139fb4a3c206229d6b9f36ff6",
|
||||||
"sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2",
|
"sha256:b2fea9dc8e3c0f32c38124790ef16cba2ee0628fe2022a52e435e1117bfef9b1",
|
||||||
"sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5",
|
"sha256:c27f371f0159feb70e6ea52ed7e768b3f3a4c5676c1900a7e51a24740381650e",
|
||||||
"sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1",
|
"sha256:c3325803095fb4c2a48649c321d2fbde59f8fbfcb9bfc7a86df27d112831c571",
|
||||||
"sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462",
|
"sha256:e474b63bab0a2ea32a7b26a4d8eec59e33e709321e5e16fb66e766b61b82a95e",
|
||||||
"sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97",
|
"sha256:e894e9ae603e963f0842498c4cd5d39c6a60f0d7e4c103df50ee939564298658"
|
||||||
"sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"
|
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.21.12"
|
"version": "==4.22.0"
|
||||||
},
|
},
|
||||||
"pycodestyle": {
|
"pycodestyle": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -519,11 +517,11 @@
|
||||||
},
|
},
|
||||||
"pylint": {
|
"pylint": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:bad9d7c36037f6043a1e848a43004dfd5ea5ceb05815d713ba56ca4503a9fe37",
|
"sha256:13b2c805a404a9bf57d002cd5f054ca4d40b0b87542bdaba5e05321ae8262c84",
|
||||||
"sha256:ffe7fa536bb38ba35006a7c8a6d2efbfdd3d95bbf21199cad31f76b1c50aaf30"
|
"sha256:ff22dde9c2128cd257c145cfd51adeff0be7df4d80d669055f24a962b351bbe4"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.16.1"
|
"version": "==2.16.2"
|
||||||
},
|
},
|
||||||
"pyproject-hooks": {
|
"pyproject-hooks": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -559,11 +557,11 @@
|
||||||
},
|
},
|
||||||
"setuptools": {
|
"setuptools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:16ccf598aab3b506593c17378473978908a2734d7336755a8769b480906bec1c",
|
"sha256:95f00380ef2ffa41d9bba85d95b27689d923c93dfbafed4aecd7cf988a25e012",
|
||||||
"sha256:b440ee5f7e607bb8c9de15259dba2583dd41a38879a7abc1d43a71c59524da48"
|
"sha256:bb6d8e508de562768f2027902929f8523932fcd1fb784e6d573d2cafac995a48"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==67.2.0"
|
"version": "==67.3.2"
|
||||||
},
|
},
|
||||||
"setuptools-git-versioning": {
|
"setuptools-git-versioning": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
@ -583,19 +581,19 @@
|
||||||
},
|
},
|
||||||
"types-protobuf": {
|
"types-protobuf": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:46ffa6647e2f8d53a4828e905f8fb0e8ff8c918309b425572dd34ab4d0b48553",
|
"sha256:39167012ead0bc5920b6322a1e4dc2d088f66a34b84cce39bb88500e49ac955a",
|
||||||
"sha256:819a7c67e69476e39c3f0c9871bbb9ee82313645d317b6daeb60ac95a309dbd3"
|
"sha256:8c105b906569e9d53ba033465880d9ef17a59bf3ba8ab656d24c9eadb9d8a056"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.21.0.5"
|
"version": "==4.21.0.6"
|
||||||
},
|
},
|
||||||
"typing-extensions": {
|
"typing-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
|
"sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb",
|
||||||
"sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
|
"sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==4.4.0"
|
"version": "==4.5.0"
|
||||||
},
|
},
|
||||||
"wheel": {
|
"wheel": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -14,7 +14,7 @@
|
||||||
[](https://stand-with-ukraine.pp.ua)
|
[](https://stand-with-ukraine.pp.ua)
|
||||||
<!-- 
|
<!-- 
|
||||||
[](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
|
[](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!-- [](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
|
<!-- [](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
|
||||||
|
|
||||||
|
|
@ -228,7 +228,7 @@ OpenCV requires [Visual C++ redistributable 2015](https://www.microsoft.com/en-u
|
||||||
|
|
||||||
## Program help: arguments and options
|
## Program help: arguments and options
|
||||||
|
|
||||||
<pre>usage: extract_otp_secrets.py [-h] [--csv FILE] [--keepass FILE] [--json FILE] [--printqr] [--saveqr DIR] [--camera NUMBER] [--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}] [-i] [--no-color] [--version] [-d | -v | -q] [infile ...]
|
<pre>usage: extract_otp_secrets.py [-h] [--csv FILE] [--keepass FILE] [--json FILE] [--txt FILE] [--printqr] [--saveqr DIR] [--camera NUMBER] [--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}] [-i] [--no-color] [--version] [-d | -v | -q] [infile ...]
|
||||||
|
|
||||||
Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps
|
Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps
|
||||||
If no infiles are provided, a GUI window starts and QR codes are captured from the camera.
|
If no infiles are provided, a GUI window starts and QR codes are captured from the camera.
|
||||||
|
|
@ -242,8 +242,9 @@ options:
|
||||||
--csv FILE, -c FILE export csv file or - for stdout
|
--csv FILE, -c FILE export csv file or - for stdout
|
||||||
--keepass FILE, -k FILE export totp/hotp csv file(s) for KeePass, - for stdout
|
--keepass FILE, -k FILE export totp/hotp csv file(s) for KeePass, - for stdout
|
||||||
--json FILE, -j FILE export json file or - for stdout
|
--json FILE, -j FILE export json file or - for stdout
|
||||||
--printqr, -p print QR code(s) as text to the terminal (requires qrcode module)
|
--txt FILE, -t FILE export txt file or - for stdout
|
||||||
--saveqr DIR, -s DIR save QR code(s) as images to the given folder (requires qrcode module)
|
--printqr, -p print QR code(s) as text to the terminal
|
||||||
|
--saveqr DIR, -s DIR save QR code(s) as images to directory
|
||||||
--camera NUMBER, -C NUMBER camera number of system (default camera: 0)
|
--camera NUMBER, -C NUMBER camera number of system (default camera: 0)
|
||||||
--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}, -Q {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}
|
--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}, -Q {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}
|
||||||
QR reader (default: ZBAR)
|
QR reader (default: ZBAR)
|
||||||
|
|
@ -694,7 +695,7 @@ Command for regeneration of Python code from proto3 message definition file (onl
|
||||||
|
|
||||||
protoc --plugin=protoc-gen-mypy=path/to/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python src/google_auth.proto
|
protoc --plugin=protoc-gen-mypy=path/to/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python src/google_auth.proto
|
||||||
|
|
||||||
The generated protobuf Python code was generated by protoc 21.12 (https://github.com/protocolbuffers/protobuf/releases/tag/v21.12).
|
The generated protobuf Python code was generated by protoc 22.0 (https://github.com/protocolbuffers/protobuf/releases/tag/v22.0).
|
||||||
|
|
||||||
For Python type hint generation the [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) package is used.
|
For Python type hint generation the [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) package is used.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ import re
|
||||||
import sys
|
import sys
|
||||||
import urllib.parse as urlparse
|
import urllib.parse as urlparse
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum
|
||||||
from typing import Any, List, Optional, Sequence, TextIO, Tuple, Union
|
from typing import Any, List, Optional, Sequence, TextIO, Tuple, Union, TYPE_CHECKING
|
||||||
|
|
||||||
import colorama
|
import colorama
|
||||||
from pkg_resources import DistributionNotFound, get_distribution
|
from pkg_resources import DistributionNotFound, get_distribution
|
||||||
|
|
@ -200,6 +200,7 @@ def main(sys_args: list[str]) -> None:
|
||||||
write_csv(args.csv, otps)
|
write_csv(args.csv, otps)
|
||||||
write_keepass_csv(args.keepass, otps)
|
write_keepass_csv(args.keepass, otps)
|
||||||
write_json(args.json, otps)
|
write_json(args.json, otps)
|
||||||
|
write_txt(args.txt, otps, True)
|
||||||
|
|
||||||
|
|
||||||
# workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None
|
# workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None
|
||||||
|
|
@ -262,9 +263,9 @@ def extract_otp_from_otp_url(otpauth_migration_url: str, otps: Otps, urls_count:
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print_otp(otp)
|
print_otp(otp)
|
||||||
if args.printqr:
|
if args.printqr:
|
||||||
print_qr(args, otp_url)
|
print_qr(otp_url)
|
||||||
if args.saveqr:
|
if args.saveqr:
|
||||||
save_qr(otp, args, len(otps))
|
save_qr_image(otp, args.saveqr, len(otps))
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print()
|
print()
|
||||||
elif args.ignore and not quiet:
|
elif args.ignore and not quiet:
|
||||||
|
|
@ -297,8 +298,9 @@ b) image file containing a QR code or = for stdin for an image containing a QR c
|
||||||
arg_parser.add_argument('--csv', '-c', help='export csv file or - for stdout', metavar=('FILE'))
|
arg_parser.add_argument('--csv', '-c', help='export csv file or - for stdout', metavar=('FILE'))
|
||||||
arg_parser.add_argument('--keepass', '-k', help='export totp/hotp csv file(s) for KeePass, - for stdout', metavar=('FILE'))
|
arg_parser.add_argument('--keepass', '-k', help='export totp/hotp csv file(s) for KeePass, - for stdout', metavar=('FILE'))
|
||||||
arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE'))
|
arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE'))
|
||||||
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal (requires qrcode module)', action='store_true')
|
arg_parser.add_argument('--txt', '-t', help='export txt file or - for stdout', metavar=('FILE'))
|
||||||
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to the given folder (requires qrcode module)', metavar=('DIR'))
|
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal', action='store_true')
|
||||||
|
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to directory', metavar=('DIR'))
|
||||||
if cv2_available:
|
if cv2_available:
|
||||||
arg_parser.add_argument('--camera', '-C', help='camera number of system (default camera: 0)', default=0, type=int, metavar=('NUMBER'))
|
arg_parser.add_argument('--camera', '-C', help='camera number of system (default camera: 0)', default=0, type=int, metavar=('NUMBER'))
|
||||||
if not zbar_available:
|
if not zbar_available:
|
||||||
|
|
@ -314,7 +316,7 @@ b) image file containing a QR code or = for stdin for an image containing a QR c
|
||||||
output_group.add_argument('-q', '--quiet', help='no stdout output, except output set by -', action='store_true')
|
output_group.add_argument('-q', '--quiet', help='no stdout output, except output set by -', action='store_true')
|
||||||
args = arg_parser.parse_args(sys_args)
|
args = arg_parser.parse_args(sys_args)
|
||||||
colored = not args.no_color
|
colored = not args.no_color
|
||||||
if args.csv == '-' or args.json == '-' or args.keepass == '-':
|
if args.csv == '-' or args.json == '-' or args.keepass == '-' or args.txt == '-':
|
||||||
args.quiet = args.q = True
|
args.quiet = args.q = True
|
||||||
|
|
||||||
verbose = args.verbose if args.verbose else LogLevel.NORMAL
|
verbose = args.verbose if args.verbose else LogLevel.NORMAL
|
||||||
|
|
@ -391,7 +393,7 @@ def extract_otps_from_camera(args: Args) -> Otps:
|
||||||
|
|
||||||
cv2_print_text(img, f"Mode: {qr_mode.name} (Hit SPACE to change)", 0, TextPosition.LEFT, FONT_COLOR, 20)
|
cv2_print_text(img, f"Mode: {qr_mode.name} (Hit SPACE to change)", 0, TextPosition.LEFT, FONT_COLOR, 20)
|
||||||
cv2_print_text(img, "Press ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17)
|
cv2_print_text(img, "Press ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17)
|
||||||
cv2_print_text(img, "Press C/J/K to save as csv/json/keepass file", 2, TextPosition.LEFT, FONT_COLOR, None)
|
cv2_print_text(img, "Press C/J/K/T to save as csv/json/keepass/txt file", 2, TextPosition.LEFT, FONT_COLOR, None)
|
||||||
|
|
||||||
cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR)
|
cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR)
|
||||||
cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR)
|
cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR)
|
||||||
|
|
@ -486,6 +488,18 @@ def cv2_handle_pressed_keys(qr_mode: QRMode, otps: Otps) -> Tuple[bool, QRMode]:
|
||||||
tk_root.update()
|
tk_root.update()
|
||||||
if len(file_name) > 0:
|
if len(file_name) > 0:
|
||||||
write_keepass_csv(file_name, otps)
|
write_keepass_csv(file_name, otps)
|
||||||
|
elif (key == ord('t') or key == ord('T')) and is_not_headless():
|
||||||
|
if has_no_otps_show_warning(otps):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
file_name = tkinter.filedialog.asksaveasfilename(
|
||||||
|
title="Save extracted otp secrets as text",
|
||||||
|
defaultextension='.txt',
|
||||||
|
filetypes=[('Text', '*.txt'), ('All', '*.*')]
|
||||||
|
)
|
||||||
|
tk_root.update()
|
||||||
|
if len(file_name) > 0:
|
||||||
|
write_txt(file_name, otps, True)
|
||||||
elif key == 32:
|
elif key == 32:
|
||||||
qr_mode = next_valid_qr_mode(qr_mode, zbar_available)
|
qr_mode = next_valid_qr_mode(qr_mode, zbar_available)
|
||||||
if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}")
|
if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}")
|
||||||
|
|
@ -655,28 +669,27 @@ def build_otp_url(secret: str, raw_otp: pb.MigrationPayload.OtpParameters) -> st
|
||||||
return otp_url
|
return otp_url
|
||||||
|
|
||||||
|
|
||||||
def print_otp(otp: Otp) -> None:
|
def print_otp(otp: Otp, out: Optional[TextIO] = None) -> None:
|
||||||
print(f"Name: {otp['name']}")
|
print(f"Name: {otp['name']}", file=out)
|
||||||
print(f"Secret: {otp['secret']}")
|
print(f"Secret: {otp['secret']}", file=out)
|
||||||
if otp['issuer']: print(f"Issuer: {otp['issuer']}")
|
if otp['issuer']: print(f"Issuer: {otp['issuer']}", file=out)
|
||||||
print(f"Type: {otp['type']}")
|
print(f"Type: {otp['type']}", file=out)
|
||||||
if otp['type'] == 'hotp':
|
if otp['type'] == 'hotp':
|
||||||
print(f"Counter: {otp['counter']}")
|
print(f"Counter: {otp['counter']}", file=out)
|
||||||
if verbose:
|
if verbose:
|
||||||
print(otp['url'])
|
print(otp['url'], file=out)
|
||||||
|
|
||||||
|
|
||||||
def save_qr(otp: Otp, args: Args, j: int) -> str:
|
def save_qr_image(otp: Otp, dir: str, j: int) -> str:
|
||||||
dir = args.saveqr
|
|
||||||
if not (os.path.exists(dir)): os.makedirs(dir, exist_ok=True)
|
if not (os.path.exists(dir)): os.makedirs(dir, exist_ok=True)
|
||||||
pattern = re.compile(r'[\W_]+')
|
pattern = re.compile(r'[\W_]+')
|
||||||
file_otp_name = pattern.sub('', otp['name'])
|
file_otp_name = pattern.sub('', otp['name'])
|
||||||
file_otp_issuer = pattern.sub('', otp['issuer'])
|
file_otp_issuer = pattern.sub('', otp['issuer'])
|
||||||
save_qr_file(args, otp['url'], f"{dir}/{j}-{file_otp_name}{'-' + file_otp_issuer if file_otp_issuer else ''}.png")
|
save_qr_image_file(otp['url'], f"{dir}/{j}-{file_otp_name}{'-' + file_otp_issuer if file_otp_issuer else ''}.png")
|
||||||
return file_otp_name
|
return file_otp_name
|
||||||
|
|
||||||
|
|
||||||
def save_qr_file(args: Args, otp_url: OtpUrl, name: str) -> None:
|
def save_qr_image_file(otp_url: OtpUrl, name: str) -> None:
|
||||||
qr = QRCode()
|
qr = QRCode()
|
||||||
qr.add_data(otp_url)
|
qr.add_data(otp_url)
|
||||||
img = qr.make_image(fill_color='black', back_color='white')
|
img = qr.make_image(fill_color='black', back_color='white')
|
||||||
|
|
@ -684,10 +697,20 @@ def save_qr_file(args: Args, otp_url: OtpUrl, name: str) -> None:
|
||||||
img.save(name)
|
img.save(name)
|
||||||
|
|
||||||
|
|
||||||
def print_qr(args: Args, otp_url: str) -> None:
|
def print_qr(otp_url: str, out: Optional[TextIO] = None) -> None:
|
||||||
qr = QRCode()
|
qr = QRCode()
|
||||||
qr.add_data(otp_url)
|
qr.add_data(otp_url)
|
||||||
qr.print_ascii()
|
qr.print_ascii(out)
|
||||||
|
|
||||||
|
|
||||||
|
def write_txt(file: str, otps: Otps, write_qr: bool = False) -> None:
|
||||||
|
if file and len(file) > 0 and len(otps) > 0:
|
||||||
|
with open_file_or_stdout(file) as outfile:
|
||||||
|
for otp in otps:
|
||||||
|
print_otp(otp, outfile)
|
||||||
|
if write_qr:
|
||||||
|
print_qr(otp['url'], outfile)
|
||||||
|
print(file=outfile)
|
||||||
|
|
||||||
|
|
||||||
def write_csv(file: str, otps: Otps) -> None:
|
def write_csv(file: str, otps: Otps) -> None:
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,18 @@ _sym_db = _symbol_database.Default()
|
||||||
|
|
||||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11google_auth.proto\"\xb7\x03\n\x10MigrationPayload\x12\x37\n\x0eotp_parameters\x18\x01 \x03(\x0b\x32\x1f.MigrationPayload.OtpParameters\x12\x0f\n\x07version\x18\x02 \x01(\x05\x12\x12\n\nbatch_size\x18\x03 \x01(\x05\x12\x13\n\x0b\x62\x61tch_index\x18\x04 \x01(\x05\x12\x10\n\x08\x62\x61tch_id\x18\x05 \x01(\x05\x1a\xb7\x01\n\rOtpParameters\x12\x0e\n\x06secret\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06issuer\x18\x03 \x01(\t\x12.\n\talgorithm\x18\x04 \x01(\x0e\x32\x1b.MigrationPayload.Algorithm\x12\x0e\n\x06\x64igits\x18\x05 \x01(\x05\x12\'\n\x04type\x18\x06 \x01(\x0e\x32\x19.MigrationPayload.OtpType\x12\x0f\n\x07\x63ounter\x18\x07 \x01(\x03\",\n\tAlgorithm\x12\x10\n\x0c\x41LGO_INVALID\x10\x00\x12\r\n\tALGO_SHA1\x10\x01\"6\n\x07OtpType\x12\x0f\n\x0bOTP_INVALID\x10\x00\x12\x0c\n\x08OTP_HOTP\x10\x01\x12\x0c\n\x08OTP_TOTP\x10\x02\x62\x06proto3')
|
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11google_auth.proto\"\xb7\x03\n\x10MigrationPayload\x12\x37\n\x0eotp_parameters\x18\x01 \x03(\x0b\x32\x1f.MigrationPayload.OtpParameters\x12\x0f\n\x07version\x18\x02 \x01(\x05\x12\x12\n\nbatch_size\x18\x03 \x01(\x05\x12\x13\n\x0b\x62\x61tch_index\x18\x04 \x01(\x05\x12\x10\n\x08\x62\x61tch_id\x18\x05 \x01(\x05\x1a\xb7\x01\n\rOtpParameters\x12\x0e\n\x06secret\x18\x01 \x01(\x0c\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06issuer\x18\x03 \x01(\t\x12.\n\talgorithm\x18\x04 \x01(\x0e\x32\x1b.MigrationPayload.Algorithm\x12\x0e\n\x06\x64igits\x18\x05 \x01(\x05\x12\'\n\x04type\x18\x06 \x01(\x0e\x32\x19.MigrationPayload.OtpType\x12\x0f\n\x07\x63ounter\x18\x07 \x01(\x03\",\n\tAlgorithm\x12\x10\n\x0c\x41LGO_INVALID\x10\x00\x12\r\n\tALGO_SHA1\x10\x01\"6\n\x07OtpType\x12\x0f\n\x0bOTP_INVALID\x10\x00\x12\x0c\n\x08OTP_HOTP\x10\x01\x12\x0c\n\x08OTP_TOTP\x10\x02\x62\x06proto3')
|
||||||
|
|
||||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
|
_globals = globals()
|
||||||
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', globals())
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||||
|
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', _globals)
|
||||||
if _descriptor._USE_C_DESCRIPTORS == False:
|
if _descriptor._USE_C_DESCRIPTORS == False:
|
||||||
|
|
||||||
DESCRIPTOR._options = None
|
DESCRIPTOR._options = None
|
||||||
_MIGRATIONPAYLOAD._serialized_start=22
|
_globals['_MIGRATIONPAYLOAD']._serialized_start=22
|
||||||
_MIGRATIONPAYLOAD._serialized_end=461
|
_globals['_MIGRATIONPAYLOAD']._serialized_end=461
|
||||||
_MIGRATIONPAYLOAD_OTPPARAMETERS._serialized_start=176
|
_globals['_MIGRATIONPAYLOAD_OTPPARAMETERS']._serialized_start=176
|
||||||
_MIGRATIONPAYLOAD_OTPPARAMETERS._serialized_end=359
|
_globals['_MIGRATIONPAYLOAD_OTPPARAMETERS']._serialized_end=359
|
||||||
_MIGRATIONPAYLOAD_ALGORITHM._serialized_start=361
|
_globals['_MIGRATIONPAYLOAD_ALGORITHM']._serialized_start=361
|
||||||
_MIGRATIONPAYLOAD_ALGORITHM._serialized_end=405
|
_globals['_MIGRATIONPAYLOAD_ALGORITHM']._serialized_end=405
|
||||||
_MIGRATIONPAYLOAD_OTPTYPE._serialized_start=407
|
_globals['_MIGRATIONPAYLOAD_OTPTYPE']._serialized_start=407
|
||||||
_MIGRATIONPAYLOAD_OTPTYPE._serialized_end=461
|
_globals['_MIGRATIONPAYLOAD_OTPTYPE']._serialized_end=461
|
||||||
# @@protoc_insertion_point(module_scope)
|
# @@protoc_insertion_point(module_scope)
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,48 @@ def test_extract_json_stdout_only_comments(capsys: pytest.CaptureFixture[str]) -
|
||||||
assert captured.err == ''
|
assert captured.err == ''
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_txt(capsys: pytest.CaptureFixture[str], tmp_path: pathlib.Path) -> None:
|
||||||
|
# Arrange
|
||||||
|
output_file = str(tmp_path / 'test_example_output.txt')
|
||||||
|
|
||||||
|
# Act
|
||||||
|
extract_otp_secrets.main(['-q', '-t', output_file, 'example_export.txt'])
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
expected_txt = read_file_to_str('tests/data/printqr_output.txt')
|
||||||
|
actual_txt = read_file_to_str(output_file)
|
||||||
|
|
||||||
|
assert actual_txt == expected_txt
|
||||||
|
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
|
||||||
|
assert captured.out == ''
|
||||||
|
assert captured.err == ''
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_txt_stdout(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
|
# Act
|
||||||
|
extract_otp_secrets.main(['-t', '-', 'example_export.txt'])
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
expected_txt = read_file_to_str('tests/data/printqr_output.txt')
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
|
||||||
|
assert captured.out == expected_txt
|
||||||
|
assert captured.err == ''
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_txt_stdout_only_comments(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
|
# Act
|
||||||
|
extract_otp_secrets.main(['-t', '-', 'tests/data/only_comments.txt'])
|
||||||
|
|
||||||
|
# Assert
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
|
||||||
|
assert captured.out == ''
|
||||||
|
assert captured.err == ''
|
||||||
|
|
||||||
|
|
||||||
def test_extract_not_encoded_plus(capsys: pytest.CaptureFixture[str]) -> None:
|
def test_extract_not_encoded_plus(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
# Act
|
# Act
|
||||||
extract_otp_secrets.main(['tests/data/test_plus_problem_export.txt'])
|
extract_otp_secrets.main(['tests/data/test_plus_problem_export.txt'])
|
||||||
|
|
@ -724,8 +766,10 @@ def test_verbose_and_quiet(capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
('-k', 'outfile', False, False),
|
('-k', 'outfile', False, False),
|
||||||
('-k', '-', True, False),
|
('-k', '-', True, False),
|
||||||
('-j', 'outfile', False, False),
|
('-j', 'outfile', False, False),
|
||||||
('-s', 'outfile', False, False),
|
|
||||||
('-j', '-', True, False),
|
('-j', '-', True, False),
|
||||||
|
('-t', 'outfile', False, False),
|
||||||
|
('-t', '-', True, False),
|
||||||
|
('-s', 'outfile', False, False),
|
||||||
('-i', None, False, False),
|
('-i', None, False, False),
|
||||||
('-p', None, True, False),
|
('-p', None, True, False),
|
||||||
('-Q', 'CV2', False, False),
|
('-Q', 'CV2', False, False),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue