remove not needed code for segfault repro

cv2_segfault_repro_1
scito 1 year ago
parent 8b9f61507a
commit 438cfe7ba8

@ -1,37 +0,0 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.134.0/containers/python-3/.devcontainer/base.Dockerfile
# [Choice] Python version: 3, 3.10, ...
ARG VARIANT=3
FROM mcr.microsoft.com/vscode/devcontainers/python:${VARIANT}
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# Remove imagemagick due to https://security-tracker.debian.org/tracker/CVE-2019-10131
&& apt-get purge -y imagemagick imagemagick-6-common \
# Install common packages, non-root user
# && /bin/bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \
# Clean up
&& apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
# [Option] Install Node.js
# ARG INSTALL_NODE="false"
# ARG NODE_VERSION="lts/*"
# RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] Allow the vscode user to pip install globally w/o sudo
# ENV PIP_TARGET=/usr/local/pip-global
# ENV PYTHONPATH=${PIP_TARGET}:${PYTHONPATH}
# ENV PATH=${PIP_TARGET}/bin:${PATH}
# RUN mkdir -p ${PIP_TARGET} \
# && chown vscode:root ${PIP_TARGET} \
# && echo "if [ \"\$(stat -c '%U' ${PIP_TARGET})\" != \"vscode\" ]; then chown -R vscode:root ${PIP_TARGET}; fi" \
# | tee -a /root/.bashrc /home/vscode/.bashrc /root/.zshrc >> /home/vscode/.zshrc
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
# && rm -rf /tmp/pip-tmp
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update \
# && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>

@ -1,22 +0,0 @@
// Docu: https://containers.dev/implementors/json_reference/
{
"name": "Python 3",
"build": {
"dockerfile": "Dockerfile",
"context": "..",
//Update 'VARIANT' to pick a Python version: 3, 3.10, ...
"args": {
"VARIANT": "3.11"
}
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python"
],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand":
"python -m pip install --upgrade pip; pip install -r requirements-dev.txt; pip install -r requirements.txt",
"postStartCommand": "echo 'Happy coding'"
// Comment out to connect as root instead.
// "remoteUser": "vscode"
}

@ -1,6 +0,0 @@
[flake8]
ignore =
E701
exclude =
protobuf_generated_python
__pycache__

@ -1,30 +0,0 @@
FROM python:3.11-slim-bullseye
# https://docs.docker.com/engine/reference/builder/
# For debugging
# docker build . -t extract_otp_secrets --pull --build-arg RUN_TESTS=false
# docker run --rm -v "$(pwd)":/files:ro extract_otp_secrets
# docker run --entrypoint /extract/run_pytest.sh --rm -v "$(pwd)":/files:ro extract_otp_secrets
# docker run --entrypoint /bin/bash -it --rm -v "$(pwd)":/files:ro --device="/dev/video0:/dev/video0" --env="DISPLAY" -v /tmp/.X11-unix:/tmp/.X11-unix:ro extract_otp_secrets
WORKDIR /extract
COPY . .
ARG RUN_TESTS=true
RUN apt-get update && apt-get install -y \
libzbar0 \
libsm6 \
python3-opencv \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -r requirements.txt \
&& if [ "$RUN_TESTS" = "true" ]; then /extract/run_pytest.sh; else echo "Not running tests..."; fi
WORKDIR /files
ENTRYPOINT ["python", "/extract/src/extract_otp_secrets.py"]
LABEL org.opencontainers.image.source https://github.com/scito/extract_otp_secrets
LABEL org.opencontainers.image.license GPL-3.0+

@ -1,42 +0,0 @@
FROM python:3.11-alpine
# https://docs.docker.com/engine/reference/builder/
# For debugging
# docker run --rm -v "$(pwd)":/files:ro extract_otp_secrets_only_txt
# docker build . -t extract_otp_secrets_only_txt -f Dockerfile_only_txt --pull --build-arg RUN_TESTS=false
# docker run --entrypoint /bin/sh -it --rm -v "$(pwd)":/files:ro extract_otp_secrets_only_txt
# docker run --entrypoint /extract/run_pytest.sh --rm -v "$(pwd)":/files:ro extract_otp_secrets_only_txt extract_otp_secrets_test.py -k "not qreader" --relaxed
WORKDIR /extract
COPY . .
ARG RUN_TESTS=true
RUN apk add --no-cache \
zlib \
jpeg \
&& echo "Arch: $(apk --print-arch)" \
&& if [[ "$(apk --print-arch)" == "aarch64" ]]; then apk add --no-cache --virtual .build-deps \
gcc \
libc-dev \
python3-dev \
py3-setuptools \
zlib-dev \
jpeg-dev \
; fi \
&& pip install --no-cache-dir \
protobuf \
qrcode \
Pillow \
colorama \
&& if [[ "$(apk --print-arch)" == "aarch64" ]]; then apk del .build-deps; fi \
&& if [[ "$RUN_TESTS" == "true" ]]; then /extract/run_pytest.sh tests/extract_otp_secrets_test.py -k "not qreader" --relaxed; else echo "Not running tests..."; fi
WORKDIR /files
ENTRYPOINT ["python", "/extract/src/extract_otp_secrets.py"]
LABEL org.opencontainers.image.source https://github.com/scito/extract_otp_secrets
LABEL org.opencontainers.image.license GPL-3.0+

@ -1,27 +0,0 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
protobuf = "*"
qrcode = "*"
pillow = "*"
qreader = "*"
opencv-contrib-python = "*"
colorama = ">=0.4.6"
# for macOS: opencv-contrib-python = "<=4.7.0"
# for PYTHON <= 3.7: typing_extensions = "*"
[dev-packages]
pytest = "*"
pytest-mock = "*"
pytest-cov = "*"
wheel = "*"
flake8 = "*"
pylint = "*"
mypy = "*"
types-protobuf = "*"
[requires]
python_version = "3.11"

563
Pipfile.lock generated

@ -1,563 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "25b244c44cb891ac15ef20c4011eb043b87fb1f112396d68f470d0bb362e97f7"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.11"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"colorama": {
"hashes": [
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
],
"index": "pypi",
"version": "==0.4.6"
},
"numpy": {
"hashes": [
"sha256:0044f7d944ee882400890f9ae955220d29b33d809a038923d88e4e01d652acd9",
"sha256:0e3463e6ac25313462e04aea3fb8a0a30fb906d5d300f58b3bc2c23da6a15398",
"sha256:179a7ef0889ab769cc03573b6217f54c8bd8e16cef80aad369e1e8185f994cd7",
"sha256:2386da9a471cc00a1f47845e27d916d5ec5346ae9696e01a8a34760858fe9dd2",
"sha256:26089487086f2648944f17adaa1a97ca6aee57f513ba5f1c0b7ebdabbe2b9954",
"sha256:28bc9750ae1f75264ee0f10561709b1462d450a4808cd97c013046073ae64ab6",
"sha256:28e418681372520c992805bb723e29d69d6b7aa411065f48216d8329d02ba032",
"sha256:442feb5e5bada8408e8fcd43f3360b78683ff12a4444670a7d9e9824c1817d36",
"sha256:6ec0c021cd9fe732e5bab6401adea5a409214ca5592cd92a114f7067febcba0c",
"sha256:7094891dcf79ccc6bc2a1f30428fa5edb1e6fb955411ffff3401fb4ea93780a8",
"sha256:84e789a085aabef2f36c0515f45e459f02f570c4b4c4c108ac1179c34d475ed7",
"sha256:87a118968fba001b248aac90e502c0b13606721b1343cdaddbc6e552e8dfb56f",
"sha256:8e669fbdcdd1e945691079c2cae335f3e3a56554e06bbd45d7609a6cf568c700",
"sha256:ad2925567f43643f51255220424c23d204024ed428afc5aad0f86f3ffc080086",
"sha256:b0677a52f5d896e84414761531947c7a330d1adc07c3a4372262f25d84af7bf7",
"sha256:b07b40f5fb4fa034120a5796288f24c1fe0e0580bbfff99897ba6267af42def2",
"sha256:b09804ff570b907da323b3d762e74432fb07955701b17b08ff1b5ebaa8cfe6a9",
"sha256:b162ac10ca38850510caf8ea33f89edcb7b0bb0dfa5592d59909419986b72407",
"sha256:b31da69ed0c18be8b77bfce48d234e55d040793cebb25398e2a7d84199fbc7e2",
"sha256:caf65a396c0d1f9809596be2e444e3bd4190d86d5c1ce21f5fc4be60a3bc5b36",
"sha256:cfa1161c6ac8f92dea03d625c2d0c05e084668f4a06568b77a25a89111621566",
"sha256:dae46bed2cb79a58d6496ff6d8da1e3b95ba09afeca2e277628171ca99b99db1",
"sha256:ddc7ab52b322eb1e40521eb422c4e0a20716c271a306860979d450decbb51b8e",
"sha256:de92efa737875329b052982e37bd4371d52cabf469f83e7b8be9bb7752d67e51",
"sha256:e274f0f6c7efd0d577744f52032fdd24344f11c5ae668fe8d01aac0422611df1",
"sha256:ed5fb71d79e771ec930566fae9c02626b939e37271ec285e9efaf1b5d4370e7d",
"sha256:ef85cf1f693c88c1fd229ccd1055570cb41cdf4875873b7728b6301f12cd05bf",
"sha256:f1b739841821968798947d3afcefd386fa56da0caf97722a5de53e07c4ccedc7"
],
"markers": "python_version >= '3.10'",
"version": "==1.24.1"
},
"opencv-contrib-python": {
"hashes": [
"sha256:1a48c2f24440cfd6e49c84dbe39c39feff5efbc90be8299c76e7141973d403b6",
"sha256:2b8e3a1a7af31ebed28487d161ca4be0edd0b0e241667c6e9c842ac683313b2f",
"sha256:2f0c32b0f2f55255632a44bdcfa185f88c7fb6d2616869942aff9d5a39df4997",
"sha256:35e9a3809da10a47189c06d4d78b8e7821b9a3578dec8cbddf6ee1675bd83557",
"sha256:3a00e12546e5578f6bb7ed408c37fcfea533d74e9691cfaf40926f6b43295577",
"sha256:6d1c993811f92ddd7919314ada7b9be1f23db1c73f1384915c834dee8549c0b9",
"sha256:7a08f9d1f9dd52de63a7bb448ab7d6d4a1a85b767c2358501d968d1e4d95098d",
"sha256:7a75f1775790106e54bcfb101c0e00e1f801a57d9baebc82d0b6758fc83a4ca0",
"sha256:86f4b60b9536948f16d2170ba3a9b22d3955a957dc61a9bc56e53692c6db2c7e",
"sha256:9829e6efedde1d1b8419c5bd4d62d289ecbf44ae35b843c6da9e3cbcba1a9a8a",
"sha256:abc6adfa8694f71a4caffa922b279bd9d96954a37eee40b147f613c64310b411",
"sha256:b4033a164b2e2ea0049ba8c1194dab82dca680953ac36f33d1cc2c060906555f",
"sha256:e3967b1f3d74b8c70be724dbc07921faec87e8806cc87b2db5e7057815d6a08c",
"sha256:e770e9f653a0e5e72b973adb8213fae2df4642730ba1faf31e73a54287a4d5d4"
],
"index": "pypi",
"version": "==4.7.0.68"
},
"opencv-python": {
"hashes": [
"sha256:3a00e12546e5578f6bb7ed408c37fcfea533d74e9691cfaf40926f6b43295577",
"sha256:6d1c993811f92ddd7919314ada7b9be1f23db1c73f1384915c834dee8549c0b9",
"sha256:7a08f9d1f9dd52de63a7bb448ab7d6d4a1a85b767c2358501d968d1e4d95098d",
"sha256:86f4b60b9536948f16d2170ba3a9b22d3955a957dc61a9bc56e53692c6db2c7e",
"sha256:9829e6efedde1d1b8419c5bd4d62d289ecbf44ae35b843c6da9e3cbcba1a9a8a",
"sha256:abc6adfa8694f71a4caffa922b279bd9d96954a37eee40b147f613c64310b411",
"sha256:e770e9f653a0e5e72b973adb8213fae2df4642730ba1faf31e73a54287a4d5d4"
],
"markers": "python_version >= '3.6'",
"version": "==4.7.0.68"
},
"pillow": {
"hashes": [
"sha256:03150abd92771742d4a8cd6f2fa6246d847dcd2e332a18d0c15cc75bf6703040",
"sha256:073adb2ae23431d3b9bcbcff3fe698b62ed47211d0716b067385538a1b0f28b8",
"sha256:0b07fffc13f474264c336298d1b4ce01d9c5a011415b79d4ee5527bb69ae6f65",
"sha256:0b7257127d646ff8676ec8a15520013a698d1fdc48bc2a79ba4e53df792526f2",
"sha256:12ce4932caf2ddf3e41d17fc9c02d67126935a44b86df6a206cf0d7161548627",
"sha256:15c42fb9dea42465dfd902fb0ecf584b8848ceb28b41ee2b58f866411be33f07",
"sha256:18498994b29e1cf86d505edcb7edbe814d133d2232d256db8c7a8ceb34d18cef",
"sha256:1c7c8ae3864846fc95f4611c78129301e203aaa2af813b703c55d10cc1628535",
"sha256:22b012ea2d065fd163ca096f4e37e47cd8b59cf4b0fd47bfca6abb93df70b34c",
"sha256:276a5ca930c913f714e372b2591a22c4bd3b81a418c0f6635ba832daec1cbcfc",
"sha256:2e0918e03aa0c72ea56edbb00d4d664294815aa11291a11504a377ea018330d3",
"sha256:3033fbe1feb1b59394615a1cafaee85e49d01b51d54de0cbf6aa8e64182518a1",
"sha256:3168434d303babf495d4ba58fc22d6604f6e2afb97adc6a423e917dab828939c",
"sha256:32a44128c4bdca7f31de5be641187367fe2a450ad83b833ef78910397db491aa",
"sha256:3dd6caf940756101205dffc5367babf288a30043d35f80936f9bfb37f8355b32",
"sha256:40e1ce476a7804b0fb74bcfa80b0a2206ea6a882938eaba917f7a0f004b42502",
"sha256:41e0051336807468be450d52b8edd12ac60bebaa97fe10c8b660f116e50b30e4",
"sha256:4390e9ce199fc1951fcfa65795f239a8a4944117b5935a9317fb320e7767b40f",
"sha256:502526a2cbfa431d9fc2a079bdd9061a2397b842bb6bc4239bb176da00993812",
"sha256:51e0e543a33ed92db9f5ef69a0356e0b1a7a6b6a71b80df99f1d181ae5875636",
"sha256:57751894f6618fd4308ed8e0c36c333e2f5469744c34729a27532b3db106ee20",
"sha256:5d77adcd56a42d00cc1be30843d3426aa4e660cab4a61021dc84467123f7a00c",
"sha256:655a83b0058ba47c7c52e4e2df5ecf484c1b0b0349805896dd350cbc416bdd91",
"sha256:68943d632f1f9e3dce98908e873b3a090f6cba1cbb1b892a9e8d97c938871fbe",
"sha256:6c738585d7a9961d8c2821a1eb3dcb978d14e238be3d70f0a706f7fa9316946b",
"sha256:73bd195e43f3fadecfc50c682f5055ec32ee2c933243cafbfdec69ab1aa87cad",
"sha256:772a91fc0e03eaf922c63badeca75e91baa80fe2f5f87bdaed4280662aad25c9",
"sha256:77ec3e7be99629898c9a6d24a09de089fa5356ee408cdffffe62d67bb75fdd72",
"sha256:7db8b751ad307d7cf238f02101e8e36a128a6cb199326e867d1398067381bff4",
"sha256:801ec82e4188e935c7f5e22e006d01611d6b41661bba9fe45b60e7ac1a8f84de",
"sha256:82409ffe29d70fd733ff3c1025a602abb3e67405d41b9403b00b01debc4c9a29",
"sha256:828989c45c245518065a110434246c44a56a8b2b2f6347d1409c787e6e4651ee",
"sha256:829f97c8e258593b9daa80638aee3789b7df9da5cf1336035016d76f03b8860c",
"sha256:871b72c3643e516db4ecf20efe735deb27fe30ca17800e661d769faab45a18d7",
"sha256:89dca0ce00a2b49024df6325925555d406b14aa3efc2f752dbb5940c52c56b11",
"sha256:90fb88843d3902fe7c9586d439d1e8c05258f41da473952aa8b328d8b907498c",
"sha256:97aabc5c50312afa5e0a2b07c17d4ac5e865b250986f8afe2b02d772567a380c",
"sha256:9aaa107275d8527e9d6e7670b64aabaaa36e5b6bd71a1015ddd21da0d4e06448",
"sha256:9f47eabcd2ded7698106b05c2c338672d16a6f2a485e74481f524e2a23c2794b",
"sha256:a0a06a052c5f37b4ed81c613a455a81f9a3a69429b4fd7bb913c3fa98abefc20",
"sha256:ab388aaa3f6ce52ac1cb8e122c4bd46657c15905904b3120a6248b5b8b0bc228",
"sha256:ad58d27a5b0262c0c19b47d54c5802db9b34d38bbf886665b626aff83c74bacd",
"sha256:ae5331c23ce118c53b172fa64a4c037eb83c9165aba3a7ba9ddd3ec9fa64a699",
"sha256:af0372acb5d3598f36ec0914deed2a63f6bcdb7b606da04dc19a88d31bf0c05b",
"sha256:afa4107d1b306cdf8953edde0534562607fe8811b6c4d9a486298ad31de733b2",
"sha256:b03ae6f1a1878233ac620c98f3459f79fd77c7e3c2b20d460284e1fb370557d4",
"sha256:b0915e734b33a474d76c28e07292f196cdf2a590a0d25bcc06e64e545f2d146c",
"sha256:b4012d06c846dc2b80651b120e2cdd787b013deb39c09f407727ba90015c684f",
"sha256:b472b5ea442148d1c3e2209f20f1e0bb0eb556538690fa70b5e1f79fa0ba8dc2",
"sha256:b59430236b8e58840a0dfb4099a0e8717ffb779c952426a69ae435ca1f57210c",
"sha256:b90f7616ea170e92820775ed47e136208e04c967271c9ef615b6fbd08d9af0e3",
"sha256:b9a65733d103311331875c1dca05cb4606997fd33d6acfed695b1232ba1df193",
"sha256:bac18ab8d2d1e6b4ce25e3424f709aceef668347db8637c2296bcf41acb7cf48",
"sha256:bca31dd6014cb8b0b2db1e46081b0ca7d936f856da3b39744aef499db5d84d02",
"sha256:be55f8457cd1eac957af0c3f5ece7bc3f033f89b114ef30f710882717670b2a8",
"sha256:c7025dce65566eb6e89f56c9509d4f628fddcedb131d9465cacd3d8bac337e7e",
"sha256:c935a22a557a560108d780f9a0fc426dd7459940dc54faa49d83249c8d3e760f",
"sha256:dbb8e7f2abee51cef77673be97760abff1674ed32847ce04b4af90f610144c7b",
"sha256:e6ea6b856a74d560d9326c0f5895ef8050126acfdc7ca08ad703eb0081e82b74",
"sha256:ebf2029c1f464c59b8bdbe5143c79fa2045a581ac53679733d3a91d400ff9efb",
"sha256:f1ff2ee69f10f13a9596480335f406dd1f70c3650349e2be67ca3139280cade0"
],
"index": "pypi",
"version": "==9.3.0"
},
"protobuf": {
"hashes": [
"sha256:1f22ac0ca65bb70a876060d96d914dae09ac98d114294f77584b0d2644fa9c30",
"sha256:237216c3326d46808a9f7c26fd1bd4b20015fb6867dc5d263a493ef9a539293b",
"sha256:27f4d15021da6d2b706ddc3860fac0a5ddaba34ab679dc182b60a8bb4e1121cc",
"sha256:299ea899484ee6f44604deb71f424234f654606b983cb496ea2a53e3c63ab791",
"sha256:3d164928ff0727d97022957c2b849250ca0e64777ee31efd7d6de2e07c494717",
"sha256:6ab80df09e3208f742c98443b6166bcb70d65f52cfeb67357d52032ea1ae9bec",
"sha256:78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7",
"sha256:7cd532c4566d0e6feafecc1059d04c7915aec8e182d1cf7adee8b24ef1e2e6ab",
"sha256:89f9149e4a0169cddfc44c74f230d7743002e3aa0b9472d8c28f0388102fc4c2",
"sha256:a53fd3f03e578553623272dc46ac2f189de23862e68565e83dde203d41b76fc5",
"sha256:b135410244ebe777db80298297a97fbb4c862c881b4403b71bac9d4107d61fd1",
"sha256:b98d0148f84e3a3c569e19f52103ca1feacdac0d2df8d6533cf983d1fda28462",
"sha256:d1736130bce8cf131ac7957fa26880ca19227d4ad68b4888b3be0dea1f95df97",
"sha256:f45460f9ee70a0ec1b6694c6e4e348ad2019275680bd68a1d9314b8c7e01e574"
],
"index": "pypi",
"version": "==4.21.12"
},
"pyzbar": {
"hashes": [
"sha256:13e3ee5a2f3a545204a285f41814d5c0db571967e8d4af8699a03afc55182a9c",
"sha256:4559628b8192feb25766d954b36a3753baaf5c97c03135aec7e4a026036b475d",
"sha256:8f4c5264c9c7c6b9f20d01efc52a4eba1ded47d9ba857a94130afe33703eb518"
],
"version": "==0.1.9"
},
"qrcode": {
"hashes": [
"sha256:375a6ff240ca9bd41adc070428b5dfc1dcfbb0f2507f1ac848f6cded38956578"
],
"index": "pypi",
"version": "==7.3.1"
},
"qreader": {
"hashes": [
"sha256:f96d2879c9f47f9641da55dc3e1df765cd4d6f1cfbc8b7fa811b70c1e27d4fba"
],
"index": "pypi",
"version": "==1.3.2"
}
},
"develop": {
"astroid": {
"hashes": [
"sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907",
"sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"
],
"markers": "python_full_version >= '3.7.2'",
"version": "==2.12.13"
},
"attrs": {
"hashes": [
"sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836",
"sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"
],
"markers": "python_version >= '3.6'",
"version": "==22.2.0"
},
"coverage": {
"extras": [
"toml"
],
"hashes": [
"sha256:07bcfb1d8ac94af886b54e18a88b393f6a73d5959bb31e46644a02453c36e475",
"sha256:09f6b5a8415b6b3e136d5fec62b552972187265cb705097bf030eb9d4ffb9b60",
"sha256:0a79137fc99815fff6a852c233628e735ec15903cfd16da0f229d9c4d45926ab",
"sha256:0b4b3a4d9915b2be879aff6299c0a6129f3d08a775d5a061f503cf79571f73e4",
"sha256:1285648428a6101b5f41a18991c84f1c3959cee359e51b8375c5882fc364a13f",
"sha256:12a5aa77783d49e05439fbe6e6b427484f8a0f9f456b46a51d8aac022cfd024d",
"sha256:19ec666533f0f70a0993f88b8273057b96c07b9d26457b41863ccd021a043b9a",
"sha256:1e414dc32ee5c3f36544ea466b6f52f28a7af788653744b8570d0bf12ff34bc0",
"sha256:2c44fcfb3781b41409d0f060a4ed748537557de9362a8a9282182fafb7a76ab4",
"sha256:397b4a923cc7566bbc7ae2dfd0ba5a039b61d19c740f1373791f2ebd11caea59",
"sha256:3cfc595d2af13856505631be072835c59f1acf30028d1c860b435c5fc9c15b69",
"sha256:3dd4ee135e08037f458425b8842d24a95a0961831a33f89685ff86b77d378f89",
"sha256:486ee81fa694b4b796fc5617e376326a088f7b9729c74d9defa211813f3861e4",
"sha256:4f943a3b2bc520102dd3e0bb465e1286e12c9a54f58accd71b9e65324d9c7c01",
"sha256:63d56165a7c76265468d7e0c5548215a5ba515fc2cba5232d17df97bffa10f6c",
"sha256:66b18c3cf8bbab0cce0d7b9e4262dc830e93588986865a8c78ab2ae324b3ed56",
"sha256:691571f31ace1837838b7e421d3a09a8c00b4aac32efacb4fc9bd0a5c647d25a",
"sha256:6c5ad996c6fa4d8ed669cfa1e8551348729d008a2caf81489ab9ea67cfbc7498",
"sha256:6d55d840e1b8c0002fce66443e124e8581f30f9ead2e54fbf6709fb593181f2c",
"sha256:72d1507f152abacea81f65fee38e4ef3ac3c02ff8bc16f21d935fd3a8a4ad910",
"sha256:74f70cd92669394eaf8d7756d1b195c8032cf7bbbdfce3bc489d4e15b3b8cf73",
"sha256:830525361249dc4cd013652b0efad645a385707a5ae49350c894b67d23fbb07c",
"sha256:854f22fa361d1ff914c7efa347398374cc7d567bdafa48ac3aa22334650dfba2",
"sha256:89caf4425fe88889e2973a8e9a3f6f5f9bbe5dd411d7d521e86428c08a873a4a",
"sha256:9158f8fb06747ac17bd237930c4372336edc85b6e13bdc778e60f9d685c3ca37",
"sha256:92651580bd46519067e36493acb394ea0607b55b45bd81dd4e26379ed1871f55",
"sha256:978258fec36c154b5e250d356c59af7d4c3ba02bef4b99cda90b6029441d797d",
"sha256:9823e4789ab70f3ec88724bba1a203f2856331986cd893dedbe3e23a6cfc1e4e",
"sha256:9b373c9345c584bb4b5f5b8840df7f4ab48c4cbb7934b58d52c57020d911b856",
"sha256:a4a574a19eeb67575a5328a5760bbbb737faa685616586a9f9da4281f940109c",
"sha256:aec2d1515d9d39ff270059fd3afbb3b44e6ec5758af73caf18991807138c7118",
"sha256:b3695c4f4750bca943b3e1f74ad4be8d29e4aeab927d50772c41359107bd5d5c",
"sha256:b3763e7fcade2ff6c8e62340af9277f54336920489ceb6a8cd6cc96da52fcc62",
"sha256:b66bb21a23680dee0be66557dc6b02a3152ddb55edf9f6723fa4a93368f7158d",
"sha256:b6f22bb64cc39bcb883e5910f99a27b200fdc14cdd79df8696fa96b0005c9444",
"sha256:b77015d1cb8fe941be1222a5a8b4e3fbca88180cfa7e2d4a4e58aeabadef0ab7",
"sha256:b9ea158775c7c2d3e54530a92da79496fb3fb577c876eec761c23e028f1e216c",
"sha256:c20cfebcc149a4c212f6491a5f9ff56f41829cd4f607b5be71bb2d530ef243b1",
"sha256:cfded268092a84605f1cc19e5c737f9ce630a8900a3589e9289622db161967e9",
"sha256:d1991f1dd95eba69d2cd7708ff6c2bbd2426160ffc73c2b81f617a053ebcb1a8",
"sha256:d3022c3007d3267a880b5adcf18c2a9bf1fc64469b394a804886b401959b8742",
"sha256:d6814854c02cbcd9c873c0f3286a02e3ac1250625cca822ca6bc1018c5b19f1c",
"sha256:d87717959d4d0ee9db08a0f1d80d21eb585aafe30f9b0a54ecf779a69cb015f6",
"sha256:e00c14720b8b3b6c23b487e70bd406abafc976ddc50490f645166f111c419c39",
"sha256:e60bef2e2416f15fdc05772bf87db06c6a6f9870d1db08fdd019fbec98ae24a9",
"sha256:e78e9dcbf4f3853d3ae18a8f9272111242531535ec9e1009fa8ec4a2b74557dc",
"sha256:f66460f17c9319ea4f91c165d46840314f0a7c004720b20be58594d162a441d8",
"sha256:fa6a5a224b7f4cfb226f4fc55a57e8537fcc096f42219128c2c74c0e7d0953e1",
"sha256:fb992c47cb1e5bd6a01e97182400bcc2ba2077080a17fcd7be23aaa6e572e390",
"sha256:fd1b9c5adc066db699ccf7fa839189a649afcdd9e02cb5dc9d24e67e7922737d",
"sha256:fd556ff16a57a070ce4f31c635953cc44e25244f91a0378c6e9bdfd40fdb249f"
],
"markers": "python_version >= '3.7'",
"version": "==7.0.1"
},
"dill": {
"hashes": [
"sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0",
"sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"
],
"markers": "python_version >= '3.11'",
"version": "==0.3.6"
},
"flake8": {
"hashes": [
"sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7",
"sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"
],
"index": "pypi",
"version": "==6.0.0"
},
"iniconfig": {
"hashes": [
"sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
"sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
],
"version": "==1.1.1"
},
"isort": {
"hashes": [
"sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6",
"sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==5.11.4"
},
"lazy-object-proxy": {
"hashes": [
"sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada",
"sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d",
"sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7",
"sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe",
"sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd",
"sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c",
"sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858",
"sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288",
"sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec",
"sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f",
"sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891",
"sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c",
"sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25",
"sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156",
"sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8",
"sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f",
"sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e",
"sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0",
"sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b"
],
"markers": "python_version >= '3.7'",
"version": "==1.8.0"
},
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
],
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"mypy": {
"hashes": [
"sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d",
"sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6",
"sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf",
"sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f",
"sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813",
"sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33",
"sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad",
"sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05",
"sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297",
"sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06",
"sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd",
"sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243",
"sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305",
"sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476",
"sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711",
"sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70",
"sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5",
"sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461",
"sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab",
"sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c",
"sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d",
"sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135",
"sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93",
"sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648",
"sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a",
"sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb",
"sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3",
"sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372",
"sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb",
"sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"
],
"index": "pypi",
"version": "==0.991"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"packaging": {
"hashes": [
"sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3",
"sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"
],
"markers": "python_version >= '3.7'",
"version": "==22.0"
},
"platformdirs": {
"hashes": [
"sha256:83c8f6d04389165de7c9b6f0c682439697887bca0aa2f1c87ef1826be3584490",
"sha256:e1fea1fe471b9ff8332e229df3cb7de4f53eeea4998d3b6bfff542115e998bd2"
],
"markers": "python_version >= '3.7'",
"version": "==2.6.2"
},
"pluggy": {
"hashes": [
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
],
"markers": "python_version >= '3.6'",
"version": "==1.0.0"
},
"pycodestyle": {
"hashes": [
"sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053",
"sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"
],
"markers": "python_version >= '3.6'",
"version": "==2.10.0"
},
"pyflakes": {
"hashes": [
"sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf",
"sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.1"
},
"pylint": {
"hashes": [
"sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4",
"sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb"
],
"index": "pypi",
"version": "==2.15.9"
},
"pytest": {
"hashes": [
"sha256:892f933d339f068883b6fd5a459f03d85bfcb355e4981e146d2c7616c21fef71",
"sha256:c4014eb40e10f11f355ad4e3c2fb2c6c6d1919c73f3b5a433de4708202cade59"
],
"index": "pypi",
"version": "==7.2.0"
},
"pytest-cov": {
"hashes": [
"sha256:2feb1b751d66a8bd934e5edfa2e961d11309dc37b73b0eabe73b5945fee20f6b",
"sha256:996b79efde6433cdbd0088872dbc5fb3ed7fe1578b68cdbba634f14bb8dd0470"
],
"index": "pypi",
"version": "==4.0.0"
},
"pytest-mock": {
"hashes": [
"sha256:f4c973eeae0282963eb293eb173ce91b091a79c1334455acfac9ddee8a1c784b",
"sha256:fbbdb085ef7c252a326fd8cdcac0aa3b1333d8811f131bdcc701002e1be7ed4f"
],
"index": "pypi",
"version": "==3.10.0"
},
"tomlkit": {
"hashes": [
"sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b",
"sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"
],
"markers": "python_version >= '3.6'",
"version": "==0.11.6"
},
"types-protobuf": {
"hashes": [
"sha256:7df483d34ad3fcb1fa7fff1073560d596c9ac1f419cfa851b220c9a93386c998",
"sha256:aeefcf39d637016998b3c7b699750847071b555f7c2e0c9873d42ab6103d1a39"
],
"index": "pypi",
"version": "==4.21.0.2"
},
"typing-extensions": {
"hashes": [
"sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa",
"sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"
],
"markers": "python_version >= '3.7'",
"version": "==4.4.0"
},
"wheel": {
"hashes": [
"sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac",
"sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8"
],
"index": "pypi",
"version": "==0.38.4"
},
"wrapt": {
"hashes": [
"sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3",
"sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b",
"sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4",
"sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2",
"sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656",
"sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3",
"sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff",
"sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310",
"sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a",
"sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57",
"sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069",
"sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383",
"sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe",
"sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87",
"sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d",
"sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b",
"sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907",
"sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f",
"sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0",
"sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28",
"sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1",
"sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853",
"sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc",
"sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3",
"sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3",
"sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164",
"sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1",
"sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c",
"sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1",
"sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7",
"sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1",
"sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320",
"sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed",
"sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1",
"sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248",
"sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c",
"sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456",
"sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77",
"sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef",
"sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1",
"sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7",
"sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86",
"sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4",
"sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d",
"sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d",
"sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8",
"sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5",
"sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471",
"sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00",
"sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68",
"sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3",
"sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d",
"sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735",
"sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d",
"sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569",
"sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7",
"sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59",
"sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5",
"sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb",
"sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b",
"sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f",
"sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462",
"sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015",
"sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"
],
"markers": "python_version >= '3.11'",
"version": "==1.14.1"
}
}
}

@ -1,495 +0,0 @@
# Extract TOTP/HOTP secret keys from Google Authenticator
[![CI Status](https://github.com/scito/extract_otp_secret_keys/actions/workflows/ci.yml/badge.svg)](https://github.com/scito/extract_otp_secret_keys/actions/workflows/ci.yml)
![coverage](https://img.shields.io/badge/coverage-95%25-brightgreen)
[![docker](https://github.com/scito/extract_otp_secret_keys/actions/workflows/ci_docker.yml/badge.svg)](https://github.com/scito/extract_otp_secret_keys/actions/workflows/ci_docker.yml)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/protobuf)
[![GitHub Pipenv locked Python version](https://img.shields.io/github/pipenv/locked/python-version/scito/extract_otp_secret_keys)](https://github.com/scito/extract_otp_secret_keys/blob/master/Pipfile.lock)
![protobuf version](https://img.shields.io/badge/protobuf-4.21.12-informational)
[![License](https://img.shields.io/github/license/scito/extract_otp_secret_keys)](https://github.com/scito/extract_otp_secret_keys/blob/master/LICENSE)
[![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/scito/extract_otp_secret_keys?sort=semver&label=version)](https://github.com/scito/extract_otp_secret_keys/tags)
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)
---
TODO add src/
TODO rename extract_otp_secret_keys
Extract two-factor authentication (2FA, TFA, OTP) secret keys from export QR codes of "Google Authenticator" app.
The secret and otp values can be printed and exported to json or csv. The QR codes can be printed or saved as PNG images.
## Installation
git clone https://github.com/scito/extract_otp_secret_keys.git
cd extract_otp_secret_keys
## Usage
### Capture QR codes from camera
1. Open "Google Authenticator" app on the mobile phone
2. Export the QR codes from "Google Authenticator" app
3. Point the QR codes to the camera of your computer
4. Call this script with the file as input:
python extract_otp_secrets.py
### With builtin QR decoder from image files
1. Open "Google Authenticator" app on the mobile phone
2. Export the QR codes from "Google Authenticator" app
4. Save the captured QR codes as image files, e.g. example_export.png
5. Transfer the images files to the computer where his script is installed.
6. Call this script with the file as input:
python extract_otp_secrets.py example_export.png
### With external QR decoder app from text files
1. Open "Google Authenticator" app on the mobile phone
2. Export the QR codes from "Google Authenticator" app
3. Read QR codes with a QR code reader (e.g. from another phone)
4. Save the captured QR codes in the QR code reader to a text file, e.g. example_export.txt. Save each QR code on a new line. (The captured QR codes look like `otpauth-migration://offline?data=...`)
5. Transfer the file to the computer where his script is installed.
6. Call this script with the file as input:
python extract_otp_secrets.py example_export.txt
## Program help: arguments and options
<pre>usage: extract_otp_secrets.py [-h] [--camera NUMBER] [--qr {QREADER,DEEP_QREADER,ZBAR,CV2,WECHAT}] [--json FILE] [--csv FILE] [--keepass FILE] [--printqr] [--saveqr DIR] [--verbose | --quiet] [infile ...]
Extracts one time password (OTP) secret keys from QR codes, e.g. from Google Authenticator app.
If no infiles are provided, the QR codes are interactively captured from the camera.
positional arguments:
infile a) file or - for stdin with 'otpauth-migration://...' URLs separated by newlines, lines starting with # are ignored;
b) image file containing a QR code or = for stdin for an image containing a QR code
options:
-h, --help show this help message and exit
--camera NUMBER, -C NUMBER camera number of system (default camera: 0)
--qr {QREADER,DEEP_QREADER,ZBAR,CV2,WECHAT}, -Q {QREADER,DEEP_QREADER,ZBAR,CV2,WECHAT}
QR reader (default: ZBAR)
--json FILE, -j FILE export json 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
--printqr, -p print QR code(s) as text to the terminal (requires qrcode module)
--saveqr DIR, -s DIR save QR code(s) as images to the given folder (requires qrcode module)
--verbose, -v verbose output
--quiet, -q no stdout output, except output set by -
examples:
python extract_otp_secrets.py
python extract_otp_secrets.py example_*.txt
python extract_otp_secrets.py - < example_export.txt
python extract_otp_secrets.py --csv - example_*.png | tail -n+2
python extract_otp_secrets.py = < example_export.png</pre>
## Dependencies
pip install -r requirements.txt
Known to work with
* Python 3.10.8, protobuf 4.21.9, qrcode 7.3.1, and pillow 9.2
* Python 3.11.1, protobuf 4.21.12, qrcode 7.3.1, and pillow 9.2
For protobuf versions 3.14.0 or similar or Python 3.6, use the extract_otp_secrets version 1.4.0.
### Shared libs installation for reading QR code images
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.
For a detailed installation documentation of [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar#installation).
#### Windows
The zbar DLLs are included with the Windows Python wheels. On other operating systems, you will need to install the zbar shared library.
TODO Write installation, not error message
##### Windows error message
If you see an ugly ImportError when importing [pyzbar](https://pypi.org/project/pyzbar/) on Windows you will most likely need the [Visual C++ Redistributable Packages for Visual Studio 2013](https://www.microsoft.com/en-US/download/details.aspx?id=40784). Install vcredist_x64.exe if using 64-bit Python, vcredist_x86.exe if using 32-bit Python.
```
Traceback (most recent call last):
File "C:\Users\Admin\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\pyzbar\zbar_library.py", line 58, in load
dependencies, libzbar = load_objects(Path(''))
^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\Admin\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\pyzbar\zbar_library.py", line 50, in load_objects
deps = [
^
File "C:\Users\Admin\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.11_qbz5n2kfra8p0\LocalCache\local-packages\Python311\site-packages\pyzbar\zbar_library.py", line 51, in <listcomp>
cdll.LoadLibrary(str(directory.joinpath(dep)))
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.496.0_x64__qbz5n2kfra8p0\Lib\ctypes\__init__.py", line 454, in LoadLibrary
return self._dlltype(name)
^^^^^^^^^^^^^^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.496.0_x64__qbz5n2kfra8p0\Lib\ctypes\__init__.py", line 376, in __init__
self._handle = _dlopen(self._name, mode)
^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: Could not find module 'libiconv.dll' (or one of its dependencies). Try using the full path with constructor syntax.
```
#### Linux (Debian, Ubuntu, ...)
sudo apt-get install libzbar0
#### Linux (OpenSUSE)
sudo zypper install libzbar0
#### Linux (Fedora)
sudo dnf install libzbar0
#### Mac OS X
brew install zbar
## Examples
### Printing otp secrets form text file
python extract_otp_secrets.py example_export.txt
### Printing otp secrets from image file
python extract_otp_secrets.py example_export.png
### Printing otp secrets multiple files
python extract_otp_secrets.py example_*.txt
python extract_otp_secrets.py example_*.png
python extract_otp_secrets.py example_export.*
python extract_otp_secrets.py example_*.txt example_*.png
### Printing otp secrets from stdin (text)
python extract_otp_secrets.py - < example_export.txt
### Printing otp secrets from stdin (image)
python extract_otp_secrets.py = < example_export.png
### Printing otp secrets csv to stdout
python extract_otp_secrets.py --csv - example_export.txt
### Printing otp secrets csv to stdout without header line
python extract_otp_secrets.py --csv - example_*.png | tail -n+2
### Reading from stdin and printing to stdout
cat example_*.txt | python extract_otp_secrets.py --csv - - | tail -n+2
## Features
* Free and open source
* Supports Google Authenticator exports (and compatible apps like Aegis Authenticator)
* Captures the the QR codes directly from the camera using QR code detection (based on OpenCV)
* Supports TOTP and HOTP
* Generates QR codes
* Exports to various formats:
* CSV
* JSON
* Dedicated CSV for KeePass
* QR code images
* Supports reading from stdin and writing to stdout, thus pipes can be used
* Reads QR codes images: (See [OpenCV docu](https://docs.opencv.org/4.x/d4/da8/group__imgcodecs.html#ga288b8b3da0892bd651fce07b3bbd3a56))
* Portable Network Graphics - *.png
* WebP - *.webp
* JPEG files - *.jpeg, *.jpg, *.jpe
* TIFF files - *.tiff, *.tif
* Windows bitmaps - *.bmp, *.dib
* JPEG 2000 files - *.jp2
* Portable image format - *.pbm, *.pgm, *.ppm *.pxm, *.pnm
* Errors and warnings are written to stderr and these are printed in red color on all platforms
* Many ways to run the script:
* Native Python
* pipenv
* pip
* venv
* Docker
* VSCode devcontainer
* devbox
* Compatible with major platforms:
* Linux
* macOS
* Windows
* Uses UTF-8 on all platforms
* Supports Python >= 3.7
* Written in modern Python using type hints and following best practices
* All these features are backed by tests ran nightly
* All functionality in one Python script: extract_otp_secrets.py (except protobuf generated code in protobuf_generated_python)
## KeePass
[KeePass 2.51](https://keepass.info/news/n220506_2.51.html) (released in May 2022) and newer [support the generation of OTPs (TOTP and HOTP)](https://keepass.info/help/base/placeholders.html#otp).
KeePass can generate the second factor password (2FA) if the OTP secret is stored in `TimeOtp-Secret-Base32` string field for TOTP or `HmacOtp-Secret-Base32` string field for HOTP. You view or edit them in entry dialog on the 'Advanced' tab page.
KeePass provides menu commands in the main window for generating one-time passwords ('Copy HMAC-Based OTP', 'Show HMAC-Based OTP', 'Copy Time-Based OTP', 'Show Time-Based OTP'). Furthermore, one-time passwords can be generated during auto-type using the {HMACOTP} and {TIMEOTP} placeholders.
In order to simplify the usage of the second factor password generation in KeePass a specific KeePass CSV export is available with option `-keepass` or `-k`. This KeePass CSV file can be imported by the ["Generic CSV Importer" of KeePass](https://keepass.info/help/kb/imp_csv.html).
If TOTP and HOTP entries have to be exported, then two files with an intermediate suffix .totp or .hotp will be added to the KeePass export filename.
Example:
- Only TOTP entries to export and parameter --keepass example_keepass_output.csv<br>
→ example_keepass_output.csv with TOTP entries will be exported
- Only HOTP entries to export and parameter --keepass example_keepass_output.csv<br>
→ example_keepass_output.csv with HOTP entries will be exported
- If both TOTP and HOTP entries to export and parameter --keepass example_keepass_output.csv<br>
→ example_keepass_output.totp.csv with TOTP entries will be exported<br>
→ example_keepass_output.hotp.csv with HOTP entries will be exported
Import CSV with TOTP entries in KeePass as
- Title
- User Name
- String (TimeOtp-Secret-Base32)
- Group (/)
Import CSV with HOTP entries in KeePass as
- Title
- User Name
- String (HmacOtp-Secret-Base32)
- String (HmacOtp-Counter)
- Group (/)
KeePass can be used as a backup for one time passwords (second factor) from the mobile phone.
## Technical background
The export QR code of "Google Authenticator" contains the URL `otpauth-migration://offline?data=...`.
The data parameter is a base64 encoded proto3 message (Google Protocol Buffers).
Command for regeneration of Python code from proto3 message definition file (only necessary in case of changes of the proto3 message definition or new protobuf versions):
protoc --python_out=protobuf_generated_python google_auth.proto --mypy_out=protobuf_generated_python
The generated protobuf Python code was generated by protoc 21.12 (https://github.com/protocolbuffers/protobuf/releases/tag/v21.12).
https://github.com/nipunn1313/mypy-protobuf
## References
* Proto3 documentation: https://developers.google.com/protocol-buffers/docs/pythontutorial
* Template code: https://github.com/beemdevelopment/Aegis/pull/406
## Glossary
* OTP = One-time password
* TOTP = Time-based one-time password
* HOTP = HMAC-based one-time password (using a counter)
* 2FA = Second factor authentication
* TFA = Two factor authentication
* QR code = Quick response code
## Alternative installation methods
### pip
```
pip install -U git+https://github.com/scito/extract_otp_secret_keys
python -m extract_otp_secrets
```
#### For development
```
pip install -U git+https://github.com/scito/extract_otp_secret_keys.git@support_img_read
python -m extract_otp_secrets
```
```
https://stackoverflow.com/questions/13685920/install-specific-git-commit-with-pip
-e for egg
pip install -U -e git+<https://github.com/myrepo.git#egg=packagename>
# pip install -U -e git+https://github.com/scito/extract_otp_secret_keys@$(git ls-remote git@github.com:scito/extract_otp_secret_keys@support_img_read.git | head -1 | awk '{print $1;}')#egg=extract_otp_secrets
pip3.11 install -e git+https://github.com/scito/extract_otp_secret_keys.git@$(git ls-remote git@github.com:scito/extract_otp_secret_keys.git | grep support_img_read | head -1 | awk '{print $1;}')#egg=extract_otp_secrets
python -m extract_otp_secrets
```
#### Example
```
wget https://raw.githubusercontent.com/scito/extract_otp_secret_keys/master/example_export.txt
python -m extract_otp_secrets example_export.txt
```
### local pip
```
pip install -U -e .
```
### pipenv
You can you use [Pipenv](https://github.com/pypa/pipenv) for running extract_otp_secrets.
```
pipenv --rm
pipenv install
pipenv shell
python extract_otp_secrets.py example_export.txt
```
### Visual Studio Code Remote - Containers / VSCode devcontainer
You can you use [VSCode devcontainer](https://code.visualstudio.com/docs/remote/containers-tutorial) for running extract_otp_secrets.
Requirement: Docker
1. Start VSCode
2. Open extract_otp_secrets.code-workspace
3. Open VSCode command palette (Ctrl-Shift-P)
4. Type command "Remote-Containers: Reopen in Container"
5. Open integrated bash terminal in VSCode
6. Execute: python extract_otp_secrets.py example_export.txt
### venv
Alternatively, you can use a python virtual env for the dependencies:
python -m venv venv
. venv/bin/activate
pip install -U -r requirements-dev.txt
pip install -U -r requirements.txt
The requirements\*.txt files contain all the dependencies (also the optional ones).
To leave the python virtual env just call `deactivate`.
### devbox
Install [devbox](https://github.com/jetpack-io/devbox), which is a wrapper for nix. Then enter the environment with Python and the packages installed with:
```
devbox shell
```
### Docker
Install [Docker](https://docs.docker.com/get-docker/).
Build and run the app within the container:
```bash
docker build . -t extract_otp_secrets --pull
docker run --rm -v "$(pwd)":/files:ro extract_otp_secrets example_export.txt
docker run --rm -v "$(pwd)":/files:ro extract_otp_secrets example_export.png
```
docker run --rm -v "$(pwd)":/files:ro -i extract_otp_secrets = < example_export.png
docker run --rm -v "$(pwd)":/files:ro -i --device="/dev/video0:/dev/video0" --env="DISPLAY" -v /tmp/.X11-unix:/tmp/.X11-unix:ro extract_otp_secrets
docker run --pull always --rm -v "$(pwd)":/files:ro -i --device="/dev/video0:/dev/video0" --env="DISPLAY" -v /tmp/.X11-unix:/tmp/.X11-unix:ro scit0/extract_otp_secrets
docker run --entrypoint /bin/bash -it --rm -v "$(pwd)":/files:ro extract_otp_secrets
docker run --pull always --rm -v "$(pwd)":/files:ro -i scit0/extract_otp_secrets
docker login -uscit0
docker build . -t extract_otp_secrets_only_txt -f Dockerfile_only_txt --pull
docker build . -t extract_otp_secrets_only_txt -f Dockerfile_only_txt --pull --build-arg RUN_TESTS=false
docker run --entrypoint /extract/run_pytest.sh --rm -v "$(pwd)":/files:ro scit0/extract_otp_secrets_only_txt extract_otp_secrets_test.py -k "not qreader" --relaxed
docker run --rm -v "$(pwd)":/files:ro extract_otp_secrets_only_txt example_export.txt
docker run --rm -v "$(pwd)":/files:ro -i extract_otp_secrets_only_txt - < example_export.txt
docker build . -t extract_otp_secrets_only_txt -f Dockerfile_only_txt --pull && docker run --entrypoint /extract/run_pytest.sh --rm -v "$(pwd)":/files:ro extract_otp_secrets_only_txt extract_otp_secrets_test.py -k "not qreader" -vvv --relaxed -s
docker pull scit0/extract_otp_secrets
docker pull scit0/extract_otp_secrets_only_txt
docker pull ghcr.io/scito/extract_otp_secrets
docker pull ghcr.io/scito/extract_otp_secrets_only_txt
## Tests
### PyTest
There are basic [pytest](https://pytest.org)s, see `extract_otp_secrets_test.py`.
Run tests:
```
pytest
```
or
```
python -m pytest
```
#### Hints
Your tests can run against an installed version after executing pip install .
Your tests can run against the local copy with an editable install after executing pip install --editable .
If you dont use an editable install and are relying on the fact that Python by default puts the current directory in sys.path to import your package, you can execute python -m pytest to execute the tests against the local copy directly, without using pip.
https://docs.pytest.org/en/7.1.x/explanation/pythonpath.html#pytest-vs-python-m-pytest
### unittest
There are basic [unittest](https://docs.python.org/3.10/library/unittest.html)s, see `extract_otp_secrets_txt_unit_test.py`.
Run tests:
```
python -m unittest
```
Note the `pytest`s are preferred and more complete. For each unittest there is also a test in `pytest`.
### VSCode Setup
Setup for running the tests in VSCode.
1. Open VSCode command palette (Ctrl-Shift-P)
2. Type command "Python: Configure Tests"
3. Choose unittest or pytest. (pytest is recommended, both are supported)
4. Set ". Root" directory
## Development
### Build
```
pip install -U -e .
python src/extract_otp_secrets.py
pip wheel .
# --isolated
# --prefer-binary
python3.11 -m build --wheel
# =
pip wheel --no-deps .
```
### Upgrade pip Packages
```
pip install -U -r requirements.txt
```
## Related projects
* [ZBar](https://github.com/mchehab/zbar) is an open source software suite for reading bar codes from various sources, including webcams.
* [Aegis Authenticator](https://github.com/beemdevelopment/Aegis) is a free, secure and open source 2FA app for Android.
* [Android OTP Extractor](https://github.com/puddly/android-otp-extractor) can extract your tokens from popular Android OTP apps and export them in a standard format or just display them as QR codes for easy importing. [Requires a _rooted_ Android phone.]
* [Python QReader](https://github.com/Eric-Canas/QReader)
* [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar)
* [OpenCV](https://docs.opencv.org/4.x/) (CV2) Open Source Computer Vision library with [opencv-python](https://github.com/opencv/opencv-python)
***
# #StandWithUkraine 🇺🇦
I have Ukrainian relatives and friends.
#RussiaInvadedUkraine on 24 of February 2022, at 05:00 the armed forces of the Russian Federation attacked Ukraine. Please, stand with Ukraine, stay tuned for updates on Ukraine's official sources and channels in English and support Ukraine in its fight for freedom and democracy in Europe.

@ -1,7 +0,0 @@
{
"packages": [
"python310",
"python310Packages.protobuf",
"python310Packages.qrcode"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 KiB

@ -1,19 +0,0 @@
# 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

@ -1,2 +0,0 @@
Title,User Name,HmacOtp-Secret-Base32,HmacOtp-Counter,Group
,hotp demo,7KSQL2JTUDIS5EF65KLMRQIIGY,4,OTP/HOTP
1 Title User Name HmacOtp-Secret-Base32 HmacOtp-Counter Group
2 hotp demo 7KSQL2JTUDIS5EF65KLMRQIIGY 4 OTP/HOTP

@ -1,6 +0,0 @@
Title,User Name,TimeOtp-Secret-Base32,Group
raspberrypi,pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,OTP/TOTP
,pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,OTP/TOTP
,pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,OTP/TOTP
raspberrypi,pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,OTP/TOTP
,encoding: ¿äÄéÉ? (demo),7KSQL2JTUDIS5EF65KLMRQIIGY,OTP/TOTP
1 Title User Name TimeOtp-Secret-Base32 Group
2 raspberrypi pi@raspberrypi 7KSQL2JTUDIS5EF65KLMRQIIGY OTP/TOTP
3 pi@raspberrypi 7KSQL2JTUDIS5EF65KLMRQIIGY OTP/TOTP
4 pi@raspberrypi 7KSQL2JTUDIS5EF65KLMRQIIGY OTP/TOTP
5 raspberrypi pi@raspberrypi 7KSQL2JTUDIS5EF65KLMRQIIGY OTP/TOTP
6 encoding: ¿äÄéÉ? (demo) 7KSQL2JTUDIS5EF65KLMRQIIGY OTP/TOTP

@ -1,7 +0,0 @@
name,secret,issuer,type,counter,url
pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,raspberrypi,totp,,otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,,totp,,otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,,totp,,otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
pi@raspberrypi,7KSQL2JTUDIS5EF65KLMRQIIGY,raspberrypi,totp,,otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
hotp demo,7KSQL2JTUDIS5EF65KLMRQIIGY,,hotp,4,otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
encoding: ¿äÄéÉ? (demo),7KSQL2JTUDIS5EF65KLMRQIIGY,,totp,,otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
1 name secret issuer type counter url
2 pi@raspberrypi 7KSQL2JTUDIS5EF65KLMRQIIGY raspberrypi totp otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
3 pi@raspberrypi 7KSQL2JTUDIS5EF65KLMRQIIGY totp otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
4 pi@raspberrypi 7KSQL2JTUDIS5EF65KLMRQIIGY totp otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
5 pi@raspberrypi 7KSQL2JTUDIS5EF65KLMRQIIGY raspberrypi totp otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
6 hotp demo 7KSQL2JTUDIS5EF65KLMRQIIGY hotp 4 otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
7 encoding: ¿äÄéÉ? (demo) 7KSQL2JTUDIS5EF65KLMRQIIGY totp otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY

@ -1,50 +0,0 @@
[
{
"name": "pi@raspberrypi",
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
"issuer": "raspberrypi",
"type": "totp",
"counter": null,
"url": "otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi"
},
{
"name": "pi@raspberrypi",
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
"issuer": "",
"type": "totp",
"counter": null,
"url": "otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY"
},
{
"name": "pi@raspberrypi",
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
"issuer": "",
"type": "totp",
"counter": null,
"url": "otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY"
},
{
"name": "pi@raspberrypi",
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
"issuer": "raspberrypi",
"type": "totp",
"counter": null,
"url": "otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi"
},
{
"name": "hotp demo",
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
"issuer": "",
"type": "hotp",
"counter": 4,
"url": "otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4"
},
{
"name": "encoding: ¿äÄéÉ? (demo)",
"secret": "7KSQL2JTUDIS5EF65KLMRQIIGY",
"issuer": "",
"type": "totp",
"counter": null,
"url": "otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY"
}
]

@ -1,4 +0,0 @@
[mypy]
[mypy-protobuf_generated_python.*]
ignore_errors = True

@ -1,66 +0,0 @@
[build-system]
requires = [
"setuptools>=64.0.0", "wheel>=0.37.0", "pip",
# https://setuptools-git-versioning.readthedocs.io/en/latest/differences.html
"setuptools-git-versioning",
]
build-backend = "setuptools.build_meta"
[project]
name = "extract_otp_secrets"
# https://pypi.org/classifiers/
classifiers = [
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Environment :: X11 Applications :: Qt",
"Environment :: Win32 (MS Windows)",
"Topic :: System :: Archiving :: Backup",
"Topic :: Utilities",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Intended Audience :: End Users/Desktop",
"Intended Audience :: Developers",
"Intended Audience :: System Administrators",
"Programming Language :: Python",
"Natural Language :: English",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
]
dependencies = [
"protobuf",
"qrcode",
"Pillow",
"qreader",
"pyzbar",
"opencv-contrib-python>=4.7.0; sys_platform == 'darwin'",
"opencv-contrib-python; sys_platform != 'darwin'",
"typing_extensions; python_version<='3.7'",
"colorama>=0.4.6",
]
description = "Extract two-factor authentication (2FA, TFA, OTP) secret keys from export QR codes of 'Google Authenticator' app"
dynamic = ["version"]
keywords = ["python", "security", "json", "otp", "csv", "protobuf", "qrcode", "two-factor", "totp", "google-authenticator", "recovery", "proto3", "mfa", "two-factor-authentication", "tfa", "qr-codes", "otpauth", "2fa", "security-tools"]
license = {text = "GNU General Public License v3 (GPLv3)"}
readme = "README.md"
authors = [{name = "scito", email = "info@scito.ch"}]
maintainers = [{name = "scito", email = "info@scito.ch"}]
requires-python = ">=3.7, <4"
scripts = {extract_otp_secrets = "extract_otp_secrets:sys_main"}
urls = {Project-URL = "https://github.com/scito/extract_otp_secret_keys", Bug-Reports = "https://github.com/scito/extract_otp_secret_keys/issues", Source = "https://github.com/scito/extract_otp_secret_keys" }
# [tool.setuptools]
# Still in beta, once it is stable move config from setup.cfg to pyproject.toml
# py-modules = ["extract_otp_secrets", "protobuf_generated_python.protobuf_generated_python"]
# [tool.setuptools.dynamic]
# version = {attr = "extract_otp_secrets.VERSION"}
[tool.setuptools-git-versioning]
enabled = true
# https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure%3E
# https://docs.pytest.org/en/7.1.x/explanation/goodpractices.html#which-import-mode
[tool.pytest.ini_options]
addopts = [ "--import-mode=importlib", ]

@ -1,3 +0,0 @@
[pytest]
markers =
qreader: QR image reader tests

@ -1,10 +0,0 @@
flake8
mypy
types-protobuf
pylint
pytest
pytest-mock
pytest-cov
setuptools
wheel
build

@ -1,9 +0,0 @@
protobuf
qrcode
Pillow
qreader
opencv-contrib-python>=4.7.0; sys_platform == 'darwin'
opencv-contrib-python; sys_platform != 'darwin'
pyzbar
typing_extensions; python_version<='3.7'
colorama>=0.4.6

@ -1,3 +0,0 @@
#!/bin/sh
cd /extract
pip install -U pytest pytest-mock && pip install --no-deps . && pytest "$@"

@ -1,12 +0,0 @@
[metadata]
name = extract_otp_secrets
[options]
python_requires = >=3.7, <4
py_modules = extract_otp_secrets, protobuf_generated_python.google_auth_pb2
package_dir =
=src
# packages=find:
# [options.packages.find]
# where=src

@ -1,5 +0,0 @@
#!/usr/bin/python3
from setuptools import setup
# compatibility with legacy builds or versions of tools that dont support certain packaging standards
setup()

@ -1,666 +0,0 @@
# Extract two-factor authentication (2FA, TFA) secret keys from export QR codes of "Google Authenticator" app
#
# Usage:
# 1. Export the QR codes from "Google Authenticator" app
# 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_secrets.py example_export.txt
#
# Requirement:
# The protobuf package of Google for proto3 is required for running this script.
# pip install protobuf
#
# Optional:
# For printing QR codes, the qrcode module is required
# pip install qrcode
#
# Technical background:
# The export QR code of "Google Authenticator" contains the URL "otpauth-migration://offline?data=...".
# The data parameter is a base64 encoded proto3 message (Google Protocol Buffers).
#
# Command for regeneration of Python code from proto3 message definition file (only necessary in case of changes of the proto3 message definition):
# protoc --python_out=generated_python google_auth.proto
#
# References:
# Proto3 documentation: https://developers.google.com/protocol-buffers/docs/pythontutorial
# Template code: https://github.com/beemdevelopment/Aegis/pull/406
# Author: Scito (https://scito.ch)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations # for compatibility with PYTHON < 3.11
import argparse
import base64
import csv
import fileinput
import json
import os
import re
import sys
import urllib.parse as urlparse
from enum import Enum
from operator import add
from typing import Any, List, Optional, TextIO, Tuple, Union
# PYTHON < 3.8: compatibility
if sys.version_info >= (3, 8):
from typing import Final, TypedDict
else:
from typing_extensions import Final, TypedDict
from qrcode import QRCode # type: ignore
import protobuf_generated_python.google_auth_pb2 as pb
import colorama
try:
import cv2 # type: ignore
import numpy
try:
import pyzbar.pyzbar as zbar # type: ignore
from qreader import QReader # type: ignore
except ImportError as e:
raise SystemExit(f"""
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: {e}""")
# Types
# PYTHON > 3.9: Final[tuple[int]]
ColorBGR = Tuple[int, int, int] # RGB Color specified as Blue, Green, Red
Point = Tuple[int, int]
# CV2 camera capture constants
FONT: Final[int] = cv2.FONT_HERSHEY_PLAIN
FONT_SCALE: Final[int] = 1
FONT_THICKNESS: Final[int] = 1
FONT_LINE_STYLE: Final[int] = cv2.LINE_AA
RECT_THICKNESS: Final[int] = 5
# PYTHON <= 3.7: must use () for assignments
START_POS_TEXT: Final[Point] = (5, 20)
NORMAL_COLOR: Final[ColorBGR] = (255, 0, 255)
SUCCESS_COLOR: Final[ColorBGR] = (0, 255, 0)
FAILURE_COLOR: Final[ColorBGR] = (0, 0, 255)
FONT_DY: Final[Tuple[int, int]] = (0, cv2.getTextSize("M", FONT, FONT_SCALE, FONT_THICKNESS)[0][1] + 5)
qreader_available = True
except ImportError:
qreader_available = False
# Workaround for PYTHON < 3.10: Union[int, None] used instead of int | None
# Types
Args = argparse.Namespace
OtpUrl = str
# PYTHON > 3.7: Otp = TypedDict('Otp', {'name': str, 'secret': str, 'issuer': str, 'type': str, 'counter': int | None, 'url': OtpUrl})
Otp = TypedDict('Otp', {'name': str, 'secret': str, 'issuer': str, 'type': str, 'counter': Union[int, None], 'url': OtpUrl})
# PYTHON > 3.9: Otps = list[Otp]
Otps = List[Otp]
# PYTHON > 3.9: OtpUrls = list[OtpUrl]
OtpUrls = List[OtpUrl]
QRMode = Enum('QRMode', ['QREADER', 'DEEP_QREADER', 'ZBAR', 'CV2', 'WECHAT'], start=0)
# Constants
CAMERA: Final[str] = 'camera'
# Global variable declaration
verbose: int = 0
quiet: bool = False
colored: bool = True
def sys_main() -> None:
main(sys.argv[1:])
def main(sys_args: list[str]) -> None:
# allow to use sys.stdout with with (avoid closing)
sys.stdout.close = lambda: None # type: ignore
# set encoding to utf-8, needed for Windows
try:
sys.stdout.reconfigure(encoding='utf-8') # type: ignore
sys.stderr.reconfigure(encoding='utf-8') # type: ignore
except AttributeError: # '_io.StringIO' object has no attribute 'reconfigure'
# StringIO in tests do not have all attributes, ignore it
pass
args = parse_args(sys_args)
if colored:
colorama.just_fix_windows_console()
otps = extract_otps(args)
write_csv(args, otps)
write_keepass_csv(args, otps)
write_json(args, otps)
def parse_args(sys_args: list[str]) -> Args:
global verbose, quiet, colored
description_text = "Extracts one time password (OTP) secret keys from QR codes, e.g. from Google Authenticator app."
if qreader_available:
description_text += "\nIf no infiles are provided, the QR codes are interactively captured from the camera."
example_text = """examples:
python extract_otp_secrets.py
python extract_otp_secrets.py example_*.txt
python extract_otp_secrets.py - < example_export.txt
python extract_otp_secrets.py --csv - example_*.png | tail -n+2
python extract_otp_secrets.py = < example_export.png"""
arg_parser = argparse.ArgumentParser(formatter_class=lambda prog: argparse.RawTextHelpFormatter(prog, max_help_position=32),
description=description_text,
epilog=example_text)
arg_parser.add_argument('infile', help="""a) file or - for stdin with 'otpauth-migration://...' URLs separated by newlines, lines starting with # are ignored;
b) image file containing a QR code or = for stdin for an image containing a QR code""", nargs='*' if qreader_available else '+')
if qreader_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('--qr', '-Q', help=f'QR reader (default: {QRMode.ZBAR.name})', type=str, choices=[mode.name for mode in QRMode], default=QRMode.ZBAR.name)
arg_parser.add_argument('--json', '-j', help='export json 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('--printqr', '-p', help='print QR code(s) as text to the terminal (requires qrcode module)', action='store_true')
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('--no-color', '-n', help='do not use ANSI colors in console output', action='store_true')
output_group = arg_parser.add_mutually_exclusive_group()
output_group.add_argument('--verbose', '-v', help='verbose output', action='count')
output_group.add_argument('--quiet', '-q', help='no stdout output, except output set by -', action='store_true')
args = arg_parser.parse_args(sys_args)
if args.csv == '-' or args.json == '-' or args.keepass == '-':
args.quiet = args.q = True
verbose = args.verbose if args.verbose else 0
quiet = True if args.quiet else False
colored = not args.no_color
if verbose: print(f"QReader installed: {qreader_available}")
if qreader_available:
if verbose > 1: print(f"CV2 version: {cv2.__version__}")
if verbose: print(f"QR reading mode: {args.qr}\n")
return args
def extract_otps(args: Args) -> Otps:
if not args.infile:
return extract_otps_from_camera(args)
else:
return extract_otps_from_files(args)
def get_color(new_otps_count: int, otp_url: str) -> ColorBGR:
if new_otps_count:
return SUCCESS_COLOR
else:
if otp_url:
return FAILURE_COLOR
else:
return NORMAL_COLOR
def extract_otps_from_camera(args: Args) -> Otps:
if verbose: print("Capture QR codes from camera")
otp_urls: OtpUrls = []
otps: Otps = []
qr_mode = QRMode[args.qr]
cam = cv2.VideoCapture(args.camera)
window_name = "Extract OTP Secrets: Capture QR Codes from Camera"
cv2.namedWindow(window_name, cv2.WINDOW_AUTOSIZE)
qreader = QReader()
cv2_qr = cv2.QRCodeDetector()
cv2_qr_wechat = cv2.wechat_qrcode.WeChatQRCode()
while True:
success, img = cam.read()
new_otps_count = 0
if not success:
log_error("Failed to capture image")
break
if qr_mode in [QRMode.QREADER, QRMode.DEEP_QREADER]:
bbox, found = qreader.detect(img)
if qr_mode == QRMode.DEEP_QREADER:
otp_url = qreader.detect_and_decode(img, True)
elif qr_mode == QRMode.QREADER:
otp_url = qreader.decode(img, bbox) if found else None
if otp_url:
new_otps_count = extract_otps_from_otp_url(otp_url, otp_urls, otps, args)
if found:
cv2.rectangle(img, (bbox[0], bbox[1]), (bbox[2], bbox[3]), get_color(new_otps_count, otp_url), RECT_THICKNESS)
elif qr_mode == QRMode.ZBAR:
for qrcode in zbar.decode(img):
otp_url = qrcode.data.decode('utf-8')
pts = numpy.array([qrcode.polygon], numpy.int32)
pts = pts.reshape((-1, 1, 2))
new_otps_count = extract_otps_from_otp_url(otp_url, otp_urls, otps, args)
cv2.polylines(img, [pts], True, get_color(new_otps_count, otp_url), RECT_THICKNESS)
elif qr_mode in [QRMode.CV2, QRMode.WECHAT]:
if QRMode.CV2:
otp_url, raw_pts, _ = cv2_qr.detectAndDecode(img)
else:
otp_url, raw_pts = cv2_qr_wechat.detectAndDecode(img)
if raw_pts is not None:
if otp_url:
new_otps_count = extract_otps_from_otp_url(otp_url, otp_urls, otps, args)
pts = numpy.array([raw_pts], numpy.int32)
pts = pts.reshape((-1, 1, 2))
cv2.polylines(img, [pts], True, get_color(new_otps_count, otp_url), RECT_THICKNESS)
else:
assert False, f"Wrong QReader mode {qr_mode.name}"
cv2.putText(img, f"Mode: {qr_mode.name} (Hit space to change)", START_POS_TEXT, FONT, FONT_SCALE, NORMAL_COLOR, FONT_THICKNESS, FONT_LINE_STYLE)
cv2.putText(img, "Hit ESC to quit", tuple(map(add, START_POS_TEXT, FONT_DY)), FONT, FONT_SCALE, NORMAL_COLOR, FONT_THICKNESS, FONT_LINE_STYLE)
window_dim = cv2.getWindowImageRect(window_name)
qrcodes_text = f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured"
pos_qrcodes_text = window_dim[2] - cv2.getTextSize(qrcodes_text, FONT, FONT_SCALE, FONT_THICKNESS)[0][0] - 5, START_POS_TEXT[1]
cv2.putText(img, qrcodes_text, pos_qrcodes_text, FONT, FONT_SCALE, NORMAL_COLOR, FONT_THICKNESS, FONT_LINE_STYLE)
otps_text = f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted"
pos_otps_text = window_dim[2] - cv2.getTextSize(otps_text, FONT, FONT_SCALE, FONT_THICKNESS)[0][0] - 5, START_POS_TEXT[1] + FONT_DY[1]
cv2.putText(img, otps_text, pos_otps_text, FONT, FONT_SCALE, NORMAL_COLOR, FONT_THICKNESS, FONT_LINE_STYLE)
cv2.imshow(window_name, img)
key = cv2.waitKey(1) & 0xFF
if key == 27 or key == ord('q') or key == 13:
# ESC or Enter or q pressed
break
elif key == 32:
qr_mode = QRMode((qr_mode.value + 1) % len(QRMode))
if verbose: print(f"QR reading mode: {qr_mode}")
if cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE) < 1:
# Window close clicked
break
cam.release()
cv2.destroyAllWindows()
return otps
def extract_otps_from_otp_url(otp_url: str, otp_urls: OtpUrls, otps: Otps, args: Args) -> int:
'''Returns -1 if opt_url was already added.'''
if otp_url and verbose: print(otp_url)
if not otp_url:
return 0
if otp_url not in otp_urls:
new_otps_count = extract_otp_from_otp_url(otp_url, otps, len(otp_urls), CAMERA, args)
if new_otps_count:
otp_urls.append(otp_url)
if verbose: print(f"Extracted {new_otps_count} otp{'s'[:len(otps) != 1]}. {len(otps)} otp{'s'[:len(otps) != 1]} from {len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} extracted")
return new_otps_count
return -1
def extract_otps_from_files(args: Args) -> Otps:
otps: Otps = []
files_count = urls_count = otps_count = 0
if verbose: print(f"Input files: {args.infile}")
for infile in args.infile:
if verbose: print(f"Processing infile {infile}")
files_count += 1
for line in get_otp_urls_from_file(infile, args):
if verbose: print(line)
if line.startswith('#') or line == '': continue
urls_count += 1
otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
if verbose: print(f"{files_count} infile{'s'[:files_count != 1]} processed")
return otps
def get_otp_urls_from_file(filename: str, args: Args) -> OtpUrls:
# stdin stream cannot be rewinded, thus distinguish, use - for utf-8 stdin and = for binary image stdin
if filename != '=':
check_file_exists(filename)
lines = read_lines_from_text_file(filename)
if lines or filename == '-':
return lines
# could not process text file, try reading as image
if filename != '-' and qreader_available:
return convert_img_to_otp_url(filename, args)
return []
def read_lines_from_text_file(filename: str) -> list[str]:
if verbose: print(f"Reading lines of {filename}")
# PYTHON >= 3.10 support encoding
if sys.version_info >= (3, 10):
finput = fileinput.input(filename, encoding='utf-8')
else:
finput = fileinput.input(filename)
try:
lines = []
for line in (line.strip() for line in finput):
if verbose: print(line)
if is_binary(line):
abort("Binary input was given in stdin, please use = instead of - as infile argument for images.")
# unfortunately yield line leads to random test fails
lines.append(line)
if not lines:
log_warn(f"{filename.replace('-', 'stdin')} is empty")
except UnicodeDecodeError:
if filename == '-':
abort("Unable to open text file form stdin. "
"In case you want read an image file from stdin, you must use '=' instead of '-'.")
else: # The file is probably an image, process below
return []
finally:
finput.close()
return lines
def extract_otp_from_otp_url(otpauth_migration_url: str, otps: Otps, urls_count: int, infile: str, args: Args) -> int:
payload = get_payload_from_otp_url(otpauth_migration_url, urls_count, infile)
if not payload:
return 0
new_otps_count = 0
# pylint: disable=no-member
for raw_otp in payload.otp_parameters:
new_otps_count += 1
if verbose: print(f"\n{len(otps) + 1}. Secret")
secret = convert_secret_from_bytes_to_base32_str(raw_otp.secret)
if verbose: print('OTP enum type:', get_enum_name_by_number(raw_otp, 'type'))
otp_type = get_otp_type_str_from_code(raw_otp.type)
otp_url = build_otp_url(secret, raw_otp)
otp: Otp = {
"name": raw_otp.name,
"secret": secret,
"issuer": raw_otp.issuer,
"type": otp_type,
"counter": raw_otp.counter if raw_otp.type == 1 else None,
"url": otp_url
}
otps.append(otp)
if not quiet:
print_otp(otp)
if args.printqr:
print_qr(args, otp_url)
if args.saveqr:
save_qr(otp, args, len(otps))
if not quiet:
print()
return new_otps_count
def convert_img_to_otp_url(filename: str, args: Args) -> OtpUrls:
if verbose: print(f"Reading image {filename}")
try:
if filename != '=':
img = cv2.imread(filename)
else:
try:
stdin = sys.stdin.buffer.read()
except AttributeError:
# Workaround for pytest, since pytest cannot monkeypatch sys.stdin.buffer
stdin = sys.stdin.read() # type: ignore # Workaround for pytest fixtures
if not stdin:
log_warn("stdin is empty")
try:
img_array = numpy.frombuffer(stdin, dtype='uint8')
except TypeError as e:
abort(f"Cannot read binary stdin buffer. Exception: {e}")
if not img_array.size:
return []
img = cv2.imdecode(img_array, cv2.IMREAD_UNCHANGED)
if img is None:
abort(f"Unable to open file for reading.\ninput file: {filename}")
qr_mode = QRMode[args.qr]
otp_urls: OtpUrls = []
if qr_mode in [QRMode.QREADER, QRMode.DEEP_QREADER]:
otp_url = QReader().detect_and_decode(img, qr_mode == QRMode.DEEP_QREADER)
otp_urls.append(otp_url)
elif qr_mode == QRMode.CV2:
otp_url, _, _ = cv2.QRCodeDetector().detectAndDecode(img)
otp_urls.append(otp_url)
elif qr_mode == QRMode.WECHAT:
otp_url, _ = cv2.wechat_qrcode.WeChatQRCode().detectAndDecode(img)
otp_urls += list(otp_url)
elif qr_mode == QRMode.ZBAR:
qrcodes = zbar.decode(img)
otp_urls += [qrcode.data.decode('utf-8') for qrcode in qrcodes]
else:
assert False, f"Wrong QReader mode {qr_mode.name}"
if len(otp_urls) == 0:
abort(f"Unable to read QR Code from file.\ninput file: {filename}")
except Exception as e:
abort(f"Encountered exception '{e}'.\ninput file: {filename}")
return otp_urls
# PYTHON >= 3.10 use: pb.MigrationPayload | None
def get_payload_from_otp_url(otp_url: str, i: int, source: str) -> Optional[pb.MigrationPayload]:
if not otp_url.startswith('otpauth-migration://'):
msg = f"input is not a otpauth-migration:// url\nsource: {source}\ninput: {otp_url}"
if source == CAMERA:
log_error(f"{msg}")
return None
else:
log_warn(f"{msg}\nMaybe a wrong file was given")
parsed_url = urlparse.urlparse(otp_url)
if verbose > 2: print(f"\nDEBUG: parsed_url={parsed_url}")
try:
params = urlparse.parse_qs(parsed_url.query, strict_parsing=True)
except Exception: # Necessary for PYTHON < 3.11
params = {}
if verbose > 2: print(f"\nDEBUG: querystring params={params}")
if 'data' not in params:
log_error(f"could not parse query parameter in input url\nsource: {source}\nurl: {otp_url}")
return None
data_base64 = params['data'][0]
if verbose > 2: print(f"\nDEBUG: data_base64={data_base64}")
data_base64_fixed = data_base64.replace(' ', '+')
if verbose > 2: print(f"\nDEBUG: data_base64_fixed={data_base64_fixed}")
data = base64.b64decode(data_base64_fixed, validate=True)
payload = pb.MigrationPayload()
try:
payload.ParseFromString(data)
except Exception:
abort(f"Cannot decode otpauth-migration migration payload.\n"
f"data={data_base64}")
if verbose:
print(f"\n{i}. Payload Line", 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: Any, field_name: str) -> str:
field_value = getattr(parent, field_name)
return parent.DESCRIPTOR.fields_by_name[field_name].enum_type.values_by_number.get(field_value).name # type: ignore # generic code
def get_otp_type_str_from_code(otp_type: int) -> str:
return 'totp' if otp_type == 2 else 'hotp'
def convert_secret_from_bytes_to_base32_str(bytes: bytes) -> str:
return str(base64.b32encode(bytes), 'utf-8').replace('=', '')
def build_otp_url(secret: str, raw_otp: pb.MigrationPayload.OtpParameters) -> str:
url_params = {'secret': secret}
if raw_otp.type == 1: url_params['counter'] = str(raw_otp.counter)
if raw_otp.issuer: url_params['issuer'] = raw_otp.issuer
otp_url = f"otpauth://{get_otp_type_str_from_code(raw_otp.type)}/{urlparse.quote(raw_otp.name)}?" + urlparse.urlencode(url_params)
return otp_url
def print_otp(otp: Otp) -> None:
print(f"Name: {otp['name']}")
print(f"Secret: {otp['secret']}")
if otp['issuer']: print(f"Issuer: {otp['issuer']}")
print(f"Type: {otp['type']}")
if otp['type'] == 'hotp':
print(f"Counter: {otp['counter']}")
if verbose:
print(otp['url'])
def save_qr(otp: Otp, args: Args, j: int) -> str:
dir = args.saveqr
if not (os.path.exists(dir)): os.makedirs(dir, exist_ok=True)
pattern = re.compile(r'[\W_]+')
file_otp_name = pattern.sub('', otp['name'])
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")
return file_otp_name
def save_qr_file(args: Args, otp_url: OtpUrl, name: str) -> None:
qr = QRCode()
qr.add_data(otp_url)
img = qr.make_image(fill_color='black', back_color='white')
if verbose: print(f"Saving to {name}")
img.save(name)
def print_qr(args: Args, otp_url: str) -> None:
qr = QRCode()
qr.add_data(otp_url)
qr.print_ascii()
def write_csv(args: Args, otps: Otps) -> None:
if args.csv and len(otps) > 0:
with open_file_or_stdout_for_csv(args.csv) as outfile:
writer = csv.DictWriter(outfile, otps[0].keys())
writer.writeheader()
writer.writerows(otps)
if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to csv {args.csv}")
def write_keepass_csv(args: Args, otps: Otps) -> None:
if args.keepass and len(otps) > 0:
has_totp = has_otp_type(otps, 'totp')
has_hotp = has_otp_type(otps, 'hotp')
otp_filename_totp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "totp")
otp_filename_hotp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "hotp")
count_totp_entries = 0
count_hotp_entries = 0
if has_totp:
with open_file_or_stdout_for_csv(otp_filename_totp) as outfile:
writer = csv.DictWriter(outfile, ["Title", "User Name", "TimeOtp-Secret-Base32", "Group"])
writer.writeheader()
for otp in otps:
if otp['type'] == 'totp':
writer.writerow({
'Title': otp['issuer'],
'User Name': otp['name'],
'TimeOtp-Secret-Base32': otp['secret'] if otp['type'] == 'totp' else None,
'Group': f"OTP/{otp['type'].upper()}"
})
count_totp_entries += 1
if has_hotp:
with open_file_or_stdout_for_csv(otp_filename_hotp) as outfile:
writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"])
writer.writeheader()
for otp in otps:
if otp['type'] == 'hotp':
writer.writerow({
'Title': otp['issuer'],
'User Name': otp['name'],
'HmacOtp-Secret-Base32': otp['secret'] if otp['type'] == 'hotp' else None,
'HmacOtp-Counter': otp['counter'] if otp['type'] == 'hotp' else None,
'Group': f"OTP/{otp['type'].upper()}"
})
count_hotp_entries += 1
if not quiet:
if count_totp_entries > 0: print(f"Exported {count_totp_entries} totp entrie{'s'[:count_totp_entries != 1]} to keepass csv file {otp_filename_totp}")
if count_hotp_entries > 0: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}")
def write_json(args: Args, otps: Otps) -> None:
if args.json:
with open_file_or_stdout(args.json) as outfile:
json.dump(otps, outfile, indent=4)
if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to json {args.json}")
def has_otp_type(otps: Otps, otp_type: str) -> bool:
for otp in otps:
if otp['type'] == otp_type:
return True
return False
def add_pre_suffix(file: str, pre_suffix: str) -> str:
'''filename.ext, pre -> filename.pre.ext'''
name, ext = os.path.splitext(file)
return name + "." + pre_suffix + (ext if ext else "")
def open_file_or_stdout(filename: str) -> TextIO:
'''stdout is denoted as "-".
Note: Set before the following line:
sys.stdout.close = lambda: None'''
return open(filename, "w", encoding='utf-8') if filename != '-' else sys.stdout
def open_file_or_stdout_for_csv(filename: str) -> TextIO:
'''stdout is denoted as "-".
newline=''
Note: Set before the following line:
sys.stdout.close = lambda: None'''
return open(filename, "w", encoding='utf-8', newline='') if filename != '-' else sys.stdout
def check_file_exists(filename: str) -> None:
if filename != '-' and not os.path.isfile(filename):
abort(f"Input file provided is non-existent or not a file."
f"\ninput file: {filename}")
def is_binary(line: str) -> bool:
try:
line.startswith('#')
return False
except (UnicodeDecodeError, AttributeError, TypeError):
return True
def log_warn(msg: str) -> None:
eprint(f"{colorama.Fore.RED if colored else ''}\nWARN: {msg}{colorama.Fore.RESET if colored else ''}")
def log_error(msg: str) -> None:
eprint(f"{colorama.Fore.RED if colored else ''}\nERROR: {msg}{colorama.Fore.RESET if colored else ''}")
def eprint(*args: Any, **kwargs: Any) -> None:
'''Print to stderr.'''
print(*args, file=sys.stderr, **kwargs)
def abort(msg: str) -> None:
log_error(msg)
sys.exit(1)
if __name__ == '__main__':
sys_main()

@ -1,32 +0,0 @@
syntax = "proto3";
// Copied from: https://github.com/beemdevelopment/Aegis/blob/master/app/src/main/proto/google_auth.proto
message MigrationPayload {
enum Algorithm {
ALGO_INVALID = 0;
ALGO_SHA1 = 1;
}
enum OtpType {
OTP_INVALID = 0;
OTP_HOTP = 1;
OTP_TOTP = 2;
}
message OtpParameters {
bytes secret = 1;
string name = 2;
string issuer = 3;
Algorithm algorithm = 4;
int32 digits = 5;
OtpType type = 6;
int64 counter = 7;
}
repeated OtpParameters otp_parameters = 1;
int32 version = 2;
int32 batch_size = 3;
int32 batch_index = 4;
int32 batch_id = 5;
}

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: google_auth.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_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')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
_MIGRATIONPAYLOAD._serialized_start=22
_MIGRATIONPAYLOAD._serialized_end=461
_MIGRATIONPAYLOAD_OTPPARAMETERS._serialized_start=176
_MIGRATIONPAYLOAD_OTPPARAMETERS._serialized_end=359
_MIGRATIONPAYLOAD_ALGORITHM._serialized_start=361
_MIGRATIONPAYLOAD_ALGORITHM._serialized_end=405
_MIGRATIONPAYLOAD_OTPTYPE._serialized_start=407
_MIGRATIONPAYLOAD_OTPTYPE._serialized_end=461
# @@protoc_insertion_point(module_scope)

@ -1,108 +0,0 @@
"""
@generated by mypy-protobuf. Do not edit manually!
isort:skip_file
"""
import builtins
import collections.abc
import google.protobuf.descriptor
import google.protobuf.internal.containers
import google.protobuf.internal.enum_type_wrapper
import google.protobuf.message
import sys
import typing
if sys.version_info >= (3, 10):
import typing as typing_extensions
else:
import typing_extensions
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
@typing_extensions.final
class MigrationPayload(google.protobuf.message.Message):
"""Copied from: https://github.com/beemdevelopment/Aegis/blob/master/app/src/main/proto/google_auth.proto"""
DESCRIPTOR: google.protobuf.descriptor.Descriptor
class _Algorithm:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _AlgorithmEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._Algorithm.ValueType], builtins.type): # noqa: F821
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
ALGO_INVALID: MigrationPayload._Algorithm.ValueType # 0
ALGO_SHA1: MigrationPayload._Algorithm.ValueType # 1
class Algorithm(_Algorithm, metaclass=_AlgorithmEnumTypeWrapper): ...
ALGO_INVALID: MigrationPayload.Algorithm.ValueType # 0
ALGO_SHA1: MigrationPayload.Algorithm.ValueType # 1
class _OtpType:
ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType
class _OtpTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._OtpType.ValueType], builtins.type): # noqa: F821
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
OTP_INVALID: MigrationPayload._OtpType.ValueType # 0
OTP_HOTP: MigrationPayload._OtpType.ValueType # 1
OTP_TOTP: MigrationPayload._OtpType.ValueType # 2
class OtpType(_OtpType, metaclass=_OtpTypeEnumTypeWrapper): ...
OTP_INVALID: MigrationPayload.OtpType.ValueType # 0
OTP_HOTP: MigrationPayload.OtpType.ValueType # 1
OTP_TOTP: MigrationPayload.OtpType.ValueType # 2
@typing_extensions.final
class OtpParameters(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor
SECRET_FIELD_NUMBER: builtins.int
NAME_FIELD_NUMBER: builtins.int
ISSUER_FIELD_NUMBER: builtins.int
ALGORITHM_FIELD_NUMBER: builtins.int
DIGITS_FIELD_NUMBER: builtins.int
TYPE_FIELD_NUMBER: builtins.int
COUNTER_FIELD_NUMBER: builtins.int
secret: builtins.bytes
name: builtins.str
issuer: builtins.str
algorithm: global___MigrationPayload.Algorithm.ValueType
digits: builtins.int
type: global___MigrationPayload.OtpType.ValueType
counter: builtins.int
def __init__(
self,
*,
secret: builtins.bytes = ...,
name: builtins.str = ...,
issuer: builtins.str = ...,
algorithm: global___MigrationPayload.Algorithm.ValueType = ...,
digits: builtins.int = ...,
type: global___MigrationPayload.OtpType.ValueType = ...,
counter: builtins.int = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["algorithm", b"algorithm", "counter", b"counter", "digits", b"digits", "issuer", b"issuer", "name", b"name", "secret", b"secret", "type", b"type"]) -> None: ...
OTP_PARAMETERS_FIELD_NUMBER: builtins.int
VERSION_FIELD_NUMBER: builtins.int
BATCH_SIZE_FIELD_NUMBER: builtins.int
BATCH_INDEX_FIELD_NUMBER: builtins.int
BATCH_ID_FIELD_NUMBER: builtins.int
@property
def otp_parameters(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___MigrationPayload.OtpParameters]: ...
version: builtins.int
batch_size: builtins.int
batch_index: builtins.int
batch_id: builtins.int
def __init__(
self,
*,
otp_parameters: collections.abc.Iterable[global___MigrationPayload.OtpParameters] | None = ...,
version: builtins.int = ...,
batch_size: builtins.int = ...,
batch_index: builtins.int = ...,
batch_id: builtins.int = ...,
) -> None: ...
def ClearField(self, field_name: typing_extensions.Literal["batch_id", b"batch_id", "batch_index", b"batch_index", "batch_size", b"batch_size", "otp_parameters", b"otp_parameters", "version", b"version"]) -> None: ...
global___MigrationPayload = MigrationPayload

@ -1,15 +0,0 @@
# 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://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

Binary file not shown.

@ -1,171 +0,0 @@
QReader installed: True
QR reading mode: ZBAR
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/
# Secret key: 7KSQL2JTUDIS5EF65KLMRQIIGY
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B
1. Payload Line
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi"
issuer: "raspberrypi"
algorithm: ALGO_SHA1
digits: 1
type: OTP_TOTP
}
version: 1
batch_size: 1
batch_id: -1320898453
1. Secret
OTP enum type: OTP_TOTP
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D
2. Payload Line
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi"
algorithm: ALGO_SHA1
digits: 1
type: OTP_TOTP
}
version: 1
batch_size: 1
batch_id: -2094403140
2. Secret
OTP enum type: OTP_TOTP
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B
3. Payload Line
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi"
algorithm: ALGO_SHA1
digits: 1
type: OTP_TOTP
}
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi"
issuer: "raspberrypi"
algorithm: ALGO_SHA1
digits: 1
type: OTP_TOTP
}
version: 1
batch_size: 1
batch_id: -1822886384
3. Secret
OTP enum type: OTP_TOTP
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
4. Secret
OTP enum type: OTP_TOTP
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D
4. Payload Line
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "hotp demo"
algorithm: ALGO_SHA1
digits: 1
type: OTP_HOTP
counter: 4
}
version: 1
batch_size: 1
batch_id: -1558849573
5. Secret
OTP enum type: OTP_HOTP
Name: hotp demo
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: hotp
Counter: 4
otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
# 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
5. Payload Line
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "encoding: ¿äÄéÉ? (demo)"
algorithm: ALGO_SHA1
digits: 1
type: OTP_TOTP
}
version: 1
batch_size: 1
batch_id: -171198419
6. Secret
OTP enum type: OTP_TOTP
Name: encoding: ¿äÄéÉ? (demo)
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
otpauth://totp/encoding%3A%20%C2%BF%C3%A4%C3%84%C3%A9%C3%89%3F%20%28demo%29?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
1 infile processed

@ -1,163 +0,0 @@
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
                                             
                                             
    █▀▀▀▀▀█  ▄▀▄▄█ █▀  ▀▀▀▀▀█  ▄▄ █▀▀▀▀▀█    
    █ ███ █ ███▀ ▀█ █▄ ▀ ▀▄▀█▄▄ █ █ ███ █    
    █ ▀▀▀ █ ██▄▄ ██  ▀█▀█▄▄▀▄ ▄▄█ █ ▀▀▀ █    
    ▀▀▀▀▀▀▀ █ █ █▄▀ █ ▀▄▀▄█ █ ▀ █ ▀▀▀▀▀▀▀    
    █▄█▀▀█▀ ▄▀▀ ▄▀██▄▀ ▄█▄█▄ ▄▄   ██▀▀█▄     
    ▀  █▀▀▀  ██▀█   ▄▀▀▄ ██   ▄▀▄█ █ ▄▄▀█    
    ▄█▀█ ▄▀█▄▄   ▄▀▄▄▀ ▄█▄█▄ ▀    ▀ ▀▄▀▀█    
    ▀▄ ▄▄▄▀▄█▄██▀▄██▀ █▄█▄ ▄▄▀▄█  ▄ █▀ ▀     
    ▄ ▄█ ▀▀▄▄ ▄▄▀█▄█▀▀▄██▄▀▄▀▀ █▀█ █▄███     
    ▀▀▄▀  ▀  ▀▄▀▀█ █▀▀█ █▄    █▄██ ▀█▀▀█▀    
     ▀█▄██▀▀█  ▄▀█▀███▄▄▀▄█▄ ▀▄▀██▀▀▀▄ ▀▄    
      █▄█ ▀   ▀▀▀▄▄▄  ▀ ▀█▄ ▄▀▀█▄██ ▄  ▀▄    
    █▀ ▀  ▀▄ █▀▀█▀▀█▄ ██▄▄█▄ ▀▀▀▄█▀▀▀ ▀ ▀    
    █ █▀▄▄▀█▀█▀▀▀ ▀ ▄  ▄ ▄ ▄▄ ▀▀▀▀█▄▄▄ █▀    
    ▀ ▀▀ ▀▀ ▄  █▀▀▀  █ ▄▄▄█▄▀█▄▀█▀▀▀█ ▀▀▀    
    █▀▀▀▀▀█ ▄▀█▀█  ███▄  ▄▄▄█ ▄ █ ▀ █  ▄     
    █ ███ █ █▄█▄▄▀█▄▀ ▄▄▄ █▄ ▀█▀███▀█ ▀▀     
    █ ▀▀▀ █ ▀█▄▄▄█▀█▀ ▄  █▄▄▄   █  ▀ ██ █    
    ▀▀▀▀▀▀▀ ▀  ▀ ▀▀▀▀▀▀ ▀ ▀  ▀▀ ▀ ▀▀ ▀ ▀▀    
                                             
                                             
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
                                         
                                         
    █▀▀▀▀▀█ ▀▀██ █▄▀█ ▀▄▄▀█▀▄ █▀▀▀▀▀█    
    █ ███ █ ▄ ▀▀▀ ▀▀█▀█▀▄ ▄█▀ █ ███ █    
    █ ▀▀▀ █ █▀▄▄▄█▄▄▀ ▄ ▄ ▀▄▄ █ ▀▀▀ █    
    ▀▀▀▀▀▀▀ █ █▄▀ ▀ ▀▄█ ▀▄█ █ ▀▀▀▀▀▀▀    
    ▀▄▄▄█▄▀██▄█▀█ ▄███   █ █ ▀▀▀▀█▄▄▀    
     ▄▀   ▀  █ █▀█▀▀█▀   ▄█▀██▀█▀▀ █▄    
    █▀█ ▄▀▀▀ ███▄█▄  ▀ █▀▄█▀▀█▀▀▀█▄▀▀    
    ▀▄▀▄▄ ▀▄▄ ▀▀▄█▀██▄▄ █▄ ▄  █▀█  █▄    
    ▀██▄█▄▀█ ▄▄ ▀▀▄▄█▀ ▀▄▀█▄▀█▀▄▀ ▄      
    ▀▄▄▀▄▀▀▀▄ █  ▀█▀█▄▄ ▄█ █▄  ▀█▀ █▄    
    ▄█ ▄  ▀ ▀ ▄ ▀█ ▄ ▀▄█ ▀▄▄█ ▄█▀ ▄▄     
      ▄█▀▄▀▀ ██ ▄  ▀ █ █▀██ ███ █ ▀█▄    
    ▀▀▀  ▀▀ ▄██▀█▀███▀▄ ▄▀▀██▀▀▀█▀▄ ▀    
    █▀▀▀▀▀█ ▀█▀▄██▀█ ▀█ █ ▄ █ ▀ █  ▄     
    █ ███ █ ▀█▄▄█▀▀▄  ▀▀▄▄ ▄▀████▀▄█     
    █ ▀▀▀ █  ▄ █ █▀▀▀▄  ▄█▀▄▀ ▀ █▀▀      
    ▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀▀ ▀  ▀▀ ▀▀ ▀▀    ▀    
                                         
                                         
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
                                         
                                         
    █▀▀▀▀▀█ ▀▀██ █▄▀█ ▀▄▄▀█▀▄ █▀▀▀▀▀█    
    █ ███ █ ▄ ▀▀▀ ▀▀█▀█▀▄ ▄█▀ █ ███ █    
    █ ▀▀▀ █ █▀▄▄▄█▄▄▀ ▄ ▄ ▀▄▄ █ ▀▀▀ █    
    ▀▀▀▀▀▀▀ █ █▄▀ ▀ ▀▄█ ▀▄█ █ ▀▀▀▀▀▀▀    
    ▀▄▄▄█▄▀██▄█▀█ ▄███   █ █ ▀▀▀▀█▄▄▀    
     ▄▀   ▀  █ █▀█▀▀█▀   ▄█▀██▀█▀▀ █▄    
    █▀█ ▄▀▀▀ ███▄█▄  ▀ █▀▄█▀▀█▀▀▀█▄▀▀    
    ▀▄▀▄▄ ▀▄▄ ▀▀▄█▀██▄▄ █▄ ▄  █▀█  █▄    
    ▀██▄█▄▀█ ▄▄ ▀▀▄▄█▀ ▀▄▀█▄▀█▀▄▀ ▄      
    ▀▄▄▀▄▀▀▀▄ █  ▀█▀█▄▄ ▄█ █▄  ▀█▀ █▄    
    ▄█ ▄  ▀ ▀ ▄ ▀█ ▄ ▀▄█ ▀▄▄█ ▄█▀ ▄▄     
      ▄█▀▄▀▀ ██ ▄  ▀ █ █▀██ ███ █ ▀█▄    
    ▀▀▀  ▀▀ ▄██▀█▀███▀▄ ▄▀▀██▀▀▀█▀▄ ▀    
    █▀▀▀▀▀█ ▀█▀▄██▀█ ▀█ █ ▄ █ ▀ █  ▄     
    █ ███ █ ▀█▄▄█▀▀▄  ▀▀▄▄ ▄▀████▀▄█     
    █ ▀▀▀ █  ▄ █ █▀▀▀▄  ▄█▀▄▀ ▀ █▀▀      
    ▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀▀ ▀  ▀▀ ▀▀ ▀▀    ▀    
                                         
                                         
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
                                             
                                             
    █▀▀▀▀▀█  ▄▀▄▄█ █▀  ▀▀▀▀▀█  ▄▄ █▀▀▀▀▀█    
    █ ███ █ ███▀ ▀█ █▄ ▀ ▀▄▀█▄▄ █ █ ███ █    
    █ ▀▀▀ █ ██▄▄ ██  ▀█▀█▄▄▀▄ ▄▄█ █ ▀▀▀ █    
    ▀▀▀▀▀▀▀ █ █ █▄▀ █ ▀▄▀▄█ █ ▀ █ ▀▀▀▀▀▀▀    
    █▄█▀▀█▀ ▄▀▀ ▄▀██▄▀ ▄█▄█▄ ▄▄   ██▀▀█▄     
    ▀  █▀▀▀  ██▀█   ▄▀▀▄ ██   ▄▀▄█ █ ▄▄▀█    
    ▄█▀█ ▄▀█▄▄   ▄▀▄▄▀ ▄█▄█▄ ▀    ▀ ▀▄▀▀█    
    ▀▄ ▄▄▄▀▄█▄██▀▄██▀ █▄█▄ ▄▄▀▄█  ▄ █▀ ▀     
    ▄ ▄█ ▀▀▄▄ ▄▄▀█▄█▀▀▄██▄▀▄▀▀ █▀█ █▄███     
    ▀▀▄▀  ▀  ▀▄▀▀█ █▀▀█ █▄    █▄██ ▀█▀▀█▀    
     ▀█▄██▀▀█  ▄▀█▀███▄▄▀▄█▄ ▀▄▀██▀▀▀▄ ▀▄    
      █▄█ ▀   ▀▀▀▄▄▄  ▀ ▀█▄ ▄▀▀█▄██ ▄  ▀▄    
    █▀ ▀  ▀▄ █▀▀█▀▀█▄ ██▄▄█▄ ▀▀▀▄█▀▀▀ ▀ ▀    
    █ █▀▄▄▀█▀█▀▀▀ ▀ ▄  ▄ ▄ ▄▄ ▀▀▀▀█▄▄▄ █▀    
    ▀ ▀▀ ▀▀ ▄  █▀▀▀  █ ▄▄▄█▄▀█▄▀█▀▀▀█ ▀▀▀    
    █▀▀▀▀▀█ ▄▀█▀█  ███▄  ▄▄▄█ ▄ █ ▀ █  ▄     
    █ ███ █ █▄█▄▄▀█▄▀ ▄▄▄ █▄ ▀█▀███▀█ ▀▀     
    █ ▀▀▀ █ ▀█▄▄▄█▀█▀ ▄  █▄▄▄   █  ▀ ██ █    
    ▀▀▀▀▀▀▀ ▀  ▀ ▀▀▀▀▀▀ ▀ ▀  ▀▀ ▀ ▀▀ ▀ ▀▀    
                                             
                                             
Name: hotp demo
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: hotp
Counter: 4
                                             
                                             
    █▀▀▀▀▀█ ▄█▀▄▄ ▄▄▄██  █▄▄██ █▄ █▀▀▀▀▀█    
    █ ███ █  ▀▄   ▄▄▄  ▀▄ ▄▄▀ █▀  █ ███ █    
    █ ▀▀▀ █ ▀█ ▄▄█▄  ▄▀█▀▀██▄▄██▄ █ ▀▀▀ █    
    ▀▀▀▀▀▀▀ ▀▄▀ █▄▀▄█▄█▄█ ▀▄█▄█ █ ▀▀▀▀▀▀▀    
    █▄█ █ ▀▄▄ ▀ ▄▄███▄█▄   ▄█▀ ▀█  ▄█▄▄▀▄    
    ▄██▄▀▄▀██▄▀▄▀ ▀▀█ ▀▄ █▄▀██▄  ▀▄▀▀▀▄█▀    
    ▄ ▄█▄▀▀▀▀█▄▄▄▀▄█▄ ▄ ▄██▀█▀▄ ▀▄█▄ █▀▀█    
    ▄▀▄▀██▀█▀  ██▀▄ ▀▀ ▄▄▄█▄██ ▄▀█▄▄▄ ▀▄▀    
    █▄▀▀▀█▀█▄ ▄ ▀ ▀█ ▄ ▄█ █▄▀█▄ █▄█    ▀▄    
    ▀▀██▄█▀  ▄█▄▀▀█▄ ▄█▀██▄▄█▄  █▀▄█ ▀▀▀█    
    █████▄▀▀█▀▀█▀▀▄  ▄ ▀█▀▄ ██▄ ▄███ ▄▀█     
     ▄▄█▀▀▀█▀█▄█  ▄█▄▄█ ▀▀ ▄▀▄ ▄█▀▄▄█▀▀▄▄    
     ██▄ █▀▄▀▀ █  ▀██ █▄ ▄   █  ▀▄█▀▄█▄██    
    ▀▄▀ █ ▀▄▀▄██▄█ ▀█▀▄▄ ██▄▄▄▀ ▀▄ ▄█ ███    
    ▀  ▀▀ ▀▀▄ ▄▄▄█▄██▀▀ ▄█ ▀ █▀▀█▀▀▀█▀  █    
    █▀▀▀▀▀█  █▄█▀█▀▀█▀   ▄█ ▀▄▄▀█ ▀ █▀▀ ▀    
    █ ███ █ ▀█▀▀ ▀ █ ▄ ▄█▄█  █▄ █▀▀██▀ ██    
    █ ▀▀▀ █ ▀▄▀▄█▀▀▄  ▀▀█▄▄ ▀▄▄█   █▀▀▀▄▀    
    ▀▀▀▀▀▀▀ ▀▀▀▀  ▀ ▀  ▀▀▀   ▀▀ ▀ ▀▀▀▀ ▀▀    
                                             
                                             
Name: encoding: ¿äÄéÉ? (demo)
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
                                                 
                                                 
    █▀▀▀▀▀█ ▄▀▀  ▀  ▄▀█ ▄▄▄██▀ ▄▀█▄█▀ █▀▀▀▀▀█    
    █ ███ █ █▄▀█▄▄ ▀  ▄▀█  █  █  ▀▀▀▄ █ ███ █    
    █ ▀▀▀ █  ▄ ▀ ▀▄▀▄ ▄▄▀▄▄█▄ ▀▄ █▀▀█ █ ▀▀▀ █    
    ▀▀▀▀▀▀▀ █ █ █▄█▄▀ ▀▄▀ █▄█ ▀ █ ▀ █ ▀▀▀▀▀▀▀    
    ▀   ▄ ▀ █▀▀▄▀ ▄▄▀▀▄█▄ █▄▀▀▄█▀██▄▄█▀ ▄█▀▀▄    
    ▄▀█▄█ ▀ ▀ █▄█▄▄    ▄███▄▄▀▀▀▄▄▀▄    ▄█▀▄▄    
     ▀█▀ ▄▀▄▄█ ▄▀███▀ ▄▀█▀▄▀▄  ▀██▄▄ ▄█▀█  ▀▄    
     ▄▀ █▄▀▀ █▀▄▄▄  ▄█▄█ ▀▄ ▄▄ ▄  ▀▀█▄▄ ▀█▄▄▄    
    ▀ ▄▄▄▀▀▄▄█▀▄ ▀▀▀█▄ █▄ ▀ ▄█▄▄▀▄▀▀▀▄▄█▄ ▀ ▀    
    █ ▄   ▀█ ▄▀ ██ █ ▄▄▀▀▀███▄     ▄▄██ ██▀█▀    
    █▀█▀██▀▀███▄  ▀▀▄▄▄▄█▀ █ ▄█▄█▄▀ ▄▄▀  ▄▄ ▀    
    ▄██▄▄ ▀ ▀   ▀▀  ██▄▄▄▀▀▄█▀█▄ ▀ █▄▀▄   ▀▄▄    
      ███▄▀█▄█▄▄█ ▀█▄ ▀▄█  ▄▀▄ █▄  ▄ █▄  ▄▀▄▀    
    █  ▄ ▄▀▀▄▄█▄▄█▄█ ▄▄▄ █▄▄▀█ █▀█▄ ▄▀▀█▄▄▄▀▀    
    ▄  ▀▄▀▀ ▄▄▀██  ▀▀▄█▀▀▄ ▀▀█   ▄ ████ █▀█▀█    
    █▀▄▀█ ▀▄▄ ▄ ▀▀▀ ▄▀ ▀ █▀▄▀▀█▀▀█▄▀█ ▀▄▀▄ █     
    ▀ ▀  ▀▀ ▄ █▄▀█▀▀▄▀█ ▀▄▄█▄▀ ██ ▀██▀▀▀█▀▄▄     
    █▀▀▀▀▀█  ▀█ ▄▄██▀ ▀██▄▀██ ▄▄██ ▀█ ▀ █ ▀█     
    █ ███ █  █▄ █▀▀█▀▀▀█▀█ ▀ ▀█ █▀▀ ██▀▀▀███▀    
    █ ▀▀▀ █  ▄ ▄▀█▄▄ ▀█ ▀▀  ▄ ▀█▀ ▄▀ █▀▀██ ▀▄    
    ▀▀▀▀▀▀▀ ▀ ▀ ▀ ▀▀   ▀  ▀ ▀▀▀▀ ▀  ▀   ▀ ▀      
                                                 
                                                 

@ -1 +0,0 @@
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.

@ -1 +0,0 @@
otpauth-migration://offline?data=XXXX

@ -1 +0,0 @@
QR-Code:otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B

@ -1 +0,0 @@
otpauth-migration://offline?data=ClEKFAciUeGF4aS6IDCvMv99ySZ1ekKsEiVTZXJlbml0eUxhYnM6dGVzdDFAc2VyZW5pdHlsYWJzLmNvLnVrGgxTZXJlbml0eUxhYnMgASgBMAIKUQoUkIY8/fbrHZWTb4CBln18lvqt0HcSJVNlcmVuaXR5TGFiczp0ZXN0MkBzZXJlbml0eWxhYnMuY28udWsaDFNlcmVuaXR5TGFicyABKAEwAgpRChScf+1/Ua4d4gCY0W/7fj9VBkM9PBIlU2VyZW5pdHlMYWJzOnRlc3QzQHNlcmVuaXR5bGFicy5jby51axoMU2VyZW5pdHlMYWJzIAEoATACClEKFG6Qu0ryTSFA/l5rmvTIXtNeb5LtEiVTZXJlbml0eUxhYnM6dGVzdDRAc2VyZW5pdHlsYWJzLmNvLnVrGgxTZXJlbml0eUxhYnMgASgBMAIQARgBIAAogtTa1vz/////AQ==

@ -1 +0,0 @@
This is just a text file masquerading as an image file.

@ -1,89 +0,0 @@
# Unit test for extract_otp_secrets.py
# Run tests:
# python -m unittest
# Author: sssudame
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations # for compatibility with PYTHON < 3.11
import unittest
import extract_otp_secrets
from utils import Capturing
class TestQRImageExtract(unittest.TestCase):
def test_img_qr_reader_happy_path(self) -> None:
with Capturing() as actual_output:
extract_otp_secrets.main(['tests/data/test_googleauth_export.png'])
expected_output =\
['Name: Test1:test1@example1.com', 'Secret: JBSWY3DPEHPK3PXP', 'Issuer: Test1', 'Type: totp', '',
'Name: Test2:test2@example2.com', 'Secret: JBSWY3DPEHPK3PXQ', 'Issuer: Test2', 'Type: totp', '',
'Name: Test3:test3@example3.com', 'Secret: JBSWY3DPEHPK3PXR', 'Issuer: Test3', 'Type: totp', '']
self.assertEqual(actual_output, expected_output)
def test_img_qr_reader_no_qr_code_in_image(self) -> None:
with Capturing() as actual_output:
with self.assertRaises(SystemExit) as context:
extract_otp_secrets.main(['-n', 'tests/data/lena_std.tif'])
expected_output = ['', 'ERROR: Unable to read QR Code from file.', 'input file: tests/data/lena_std.tif']
self.assertEqual(actual_output, expected_output)
self.assertEqual(context.exception.code, 1)
def test_img_qr_reader_nonexistent_file(self) -> None:
with Capturing() as actual_output:
with self.assertRaises(SystemExit) as context:
extract_otp_secrets.main(['-n', 'nonexistent.bmp'])
expected_output = ['', 'ERROR: Input file provided is non-existent or not a file.', 'input file: nonexistent.bmp']
self.assertEqual(actual_output, expected_output)
self.assertEqual(context.exception.code, 1)
def test_img_qr_reader_non_image_file(self) -> None:
with Capturing() as actual_output:
extract_otp_secrets.main(['-n', 'tests/data/text_masquerading_as_image.jpeg'])
expected_output = [
'',
'WARN: input is not a otpauth-migration:// url',
'source: tests/data/text_masquerading_as_image.jpeg',
"input: This is just a text file masquerading as an image file.",
'Maybe a wrong file was given',
'',
'ERROR: could not parse query parameter in input url',
'source: tests/data/text_masquerading_as_image.jpeg',
"url: This is just a text file masquerading as an image file.",
]
self.assertEqual(actual_output, expected_output)
def setUp(self) -> None:
self.cleanup()
def tearDown(self) -> None:
self.cleanup()
def cleanup(self) -> None:
pass
if __name__ == '__main__':
unittest.main()

@ -1,244 +0,0 @@
# Unit test for extract_otp_secrets.py
# Run tests:
# python -m unittest
# Author: Scito (https://scito.ch)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations # for compatibility with PYTHON < 3.11
import io
import os
import sys
import unittest
from contextlib import redirect_stdout
import extract_otp_secrets
from utils import (Capturing, read_csv, read_file_to_str, read_json,
remove_dir_with_files, remove_file, count_files_in_dir)
class TestExtract(unittest.TestCase):
def test_extract_csv(self) -> None:
extract_otp_secrets.main(['-q', '-c', 'test_example_output.csv', 'example_export.txt'])
expected_csv = read_csv('example_output.csv')
actual_csv = read_csv('test_example_output.csv')
self.assertEqual(actual_csv, expected_csv)
def test_extract_json(self) -> None:
extract_otp_secrets.main(['-q', '-j', 'test_example_output.json', 'example_export.txt'])
expected_json = read_json('example_output.json')
actual_json = read_json('test_example_output.json')
self.assertEqual(actual_json, expected_json)
def test_extract_stdout_1(self) -> None:
with Capturing() as output:
extract_otp_secrets.main(['example_export.txt'])
expected_output = [
'Name: pi@raspberrypi',
'Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY',
'Issuer: raspberrypi',
'Type: totp',
'',
'Name: pi@raspberrypi',
'Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY',
'Type: totp',
'',
'Name: pi@raspberrypi',
'Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY',
'Type: totp',
'',
'Name: pi@raspberrypi',
'Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY',
'Issuer: raspberrypi',
'Type: totp',
'',
'Name: hotp demo',
'Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY',
'Type: hotp',
'Counter: 4',
'',
'Name: encoding: ¿äÄéÉ? (demo)',
'Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY',
'Type: totp',
''
]
self.assertEqual(output, expected_output)
# Ref for capturing https://stackoverflow.com/a/40984270
def test_extract_stdout_2(self) -> None:
out = io.StringIO()
with redirect_stdout(out):
extract_otp_secrets.main(['example_export.txt'])
actual_output = out.getvalue()
expected_output = '''Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi
Type: totp
Name: hotp demo
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: hotp
Counter: 4
Name: encoding: ¿äÄéÉ? (demo)
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp
'''
self.assertEqual(actual_output, expected_output)
def test_extract_not_encoded_plus(self) -> None:
out = io.StringIO()
with redirect_stdout(out):
extract_otp_secrets.main(['tests/data/test_plus_problem_export.txt'])
actual_output = out.getvalue()
expected_output = '''Name: SerenityLabs:test1@serenitylabs.co.uk
Secret: A4RFDYMF4GSLUIBQV4ZP67OJEZ2XUQVM
Issuer: SerenityLabs
Type: totp
Name: SerenityLabs:test2@serenitylabs.co.uk
Secret: SCDDZ7PW5MOZLE3PQCAZM7L4S35K3UDX
Issuer: SerenityLabs
Type: totp
Name: SerenityLabs:test3@serenitylabs.co.uk
Secret: TR76272RVYO6EAEY2FX7W7R7KUDEGPJ4
Issuer: SerenityLabs
Type: totp
Name: SerenityLabs:test4@serenitylabs.co.uk
Secret: N2ILWSXSJUQUB7S6NONPJSC62NPG7EXN
Issuer: SerenityLabs
Type: totp
'''
self.assertEqual(actual_output, expected_output)
def test_extract_printqr(self) -> None:
out = io.StringIO()
with redirect_stdout(out):
extract_otp_secrets.main(['-p', 'example_export.txt'])
actual_output = out.getvalue()
expected_output = read_file_to_str('tests/data/printqr_output.txt')
self.assertEqual(actual_output, expected_output)
def test_extract_saveqr(self) -> None:
extract_otp_secrets.main(['-q', '-s', 'testout/qr/', 'example_export.txt'])
self.assertTrue(os.path.isfile('testout/qr/1-piraspberrypi-raspberrypi.png'))
self.assertTrue(os.path.isfile('testout/qr/2-piraspberrypi.png'))
self.assertTrue(os.path.isfile('testout/qr/3-piraspberrypi.png'))
self.assertTrue(os.path.isfile('testout/qr/4-piraspberrypi-raspberrypi.png'))
self.assertTrue(os.path.isfile('testout/qr/5-hotpdemo.png'))
self.assertTrue(os.path.isfile('testout/qr/6-encodingäÄéÉdemo.png'))
self.assertEqual(count_files_in_dir('testout/qr'), 6)
def test_extract_verbose(self) -> None:
if sys.implementation.name == 'pypy' or sys.platform.startswith("win") or sys.version_info < (3, 10):
self.skipTest("Avoid encoding problems")
out = io.StringIO()
with redirect_stdout(out):
extract_otp_secrets.main(['-n', '-v', 'example_export.txt'])
actual_output = out.getvalue()
expected_output = read_file_to_str('tests/data/print_verbose_output.txt')
self.assertEqual(actual_output, expected_output)
def test_extract_debug(self) -> None:
out = io.StringIO()
with redirect_stdout(out):
extract_otp_secrets.main(['-vvv', 'example_export.txt'])
actual_output = out.getvalue()
expected_stdout = read_file_to_str('tests/data/print_verbose_output.txt')
self.assertGreater(len(actual_output), len(expected_stdout))
self.assertTrue("DEBUG: " in actual_output)
def test_extract_help_1(self) -> None:
out = io.StringIO()
with redirect_stdout(out):
try:
extract_otp_secrets.main(['-h'])
self.fail("Must abort")
except SystemExit as e:
self.assertEqual(e.code, 0)
actual_output = out.getvalue()
self.assertGreater(len(actual_output), 0)
self.assertTrue("-h, --help" in actual_output and "--verbose, -v" in actual_output)
def test_extract_help_2(self) -> None:
out = io.StringIO()
with redirect_stdout(out):
with self.assertRaises(SystemExit) as context:
extract_otp_secrets.main(['-h'])
actual_output = out.getvalue()
self.assertGreater(len(actual_output), 0)
self.assertTrue("-h, --help" in actual_output and "--verbose, -v" in actual_output)
self.assertEqual(context.exception.code, 0)
def test_extract_help_3(self) -> None:
with Capturing() as actual_output:
with self.assertRaises(SystemExit) as context:
extract_otp_secrets.main(['-h'])
self.assertGreater(len(actual_output), 0)
self.assertTrue("-h, --help" in "\n".join(actual_output) and "--verbose, -v" in "\n".join(actual_output))
self.assertEqual(context.exception.code, 0)
def setUp(self) -> None:
self.cleanup()
def tearDown(self) -> None:
self.cleanup()
def cleanup(self) -> None:
remove_file('test_example_output.csv')
remove_file('test_example_output.json')
remove_dir_with_files('testout/')
if __name__ == '__main__':
unittest.main()

@ -1,140 +0,0 @@
# Author: Scito (https://scito.ch)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from __future__ import annotations # for compatibility with PYTHON < 3.11
import csv
import glob
import io
import json
import os
import re
import shutil
import sys
import pathlib
from typing import BinaryIO, Any, Union, List
# Types
# PYTHON < 3.10: Workaround for str | pathlib.Path
PathLike = Union[str, pathlib.Path]
# Ref. https://stackoverflow.com/a/16571630
# PYTHON 3.11: class Capturing(list[Any]):
class Capturing(List[Any]):
'''Capture stdout and stderr
Usage:
with Capturing() as output:
print("Output")
'''
# TODO remove type ignore if fixed, see https://github.com/python/mypy/issues/11871, https://stackoverflow.com/questions/72174409/type-hinting-the-return-value-of-a-class-method-that-returns-self
def __enter__(self): # type: ignore
self._stdout = sys.stdout
sys.stdout = self._stringio_std = io.StringIO()
self._stderr = sys.stderr
sys.stderr = self._stringio_err = io.StringIO()
return self
def __exit__(self, *args: Any) -> None:
self.extend(self._stringio_std.getvalue().splitlines())
del self._stringio_std # free up some memory
sys.stdout = self._stdout
self.extend(self._stringio_err.getvalue().splitlines())
del self._stringio_err # free up some memory
sys.stderr = self._stderr
def file_exits(file: PathLike) -> bool:
return os.path.isfile(file)
def remove_file(file: PathLike) -> None:
if file_exits(file): os.remove(file)
def remove_files(glob_pattern: str) -> None:
for f in glob.glob(glob_pattern):
os.remove(f)
def remove_dir_with_files(dir: PathLike) -> None:
if os.path.exists(dir): shutil.rmtree(dir)
def read_csv(filename: str) -> List[List[str]]:
"""Returns a list of lines."""
with open(filename, "r", encoding="utf-8", newline='') as infile:
lines: List[List[str]] = []
reader = csv.reader(infile)
for line in reader:
lines.append(line)
return lines
def read_csv_str(data_str: str) -> List[List[str]]:
"""Returns a list of lines."""
lines: List[List[str]] = []
reader = csv.reader(data_str.splitlines())
for line in reader:
lines.append(line)
return lines
def read_json(filename: str) -> Any:
"""Returns a list or a dictionary."""
with open(filename, "r", encoding="utf-8") as infile:
return json.load(infile)
def read_json_str(data_str: str) -> Any:
"""Returns a list or a dictionary."""
return json.loads(data_str)
def read_file_to_list(filename: str) -> List[str]:
"""Returns a list of lines."""
with open(filename, "r", encoding="utf-8") as infile:
return infile.readlines()
def read_file_to_str(filename: str) -> str:
"""Returns a str."""
return "".join(read_file_to_list(filename))
def read_binary_file_as_stream(filename: str) -> BinaryIO:
"""Returns binary file content."""
with open(filename, "rb",) as infile:
return io.BytesIO(infile.read())
def replace_escaped_octal_utf8_bytes_with_str(str: str) -> str:
encoded_name_strings = re.findall(r'name: .*$', str, flags=re.MULTILINE)
for encoded_name_string in encoded_name_strings:
escaped_bytes = re.findall(r'((?:\\[0-9]+)+)', encoded_name_string)
for byte_sequence in escaped_bytes:
unicode_str = b''.join([int(byte, 8).to_bytes(1, 'little') for byte in byte_sequence.split('\\') if byte]).decode('utf-8')
print("Replace '{}' by '{}'".format(byte_sequence, unicode_str))
str = str.replace(byte_sequence, unicode_str)
return str
def quick_and_dirty_workaround_encoding_problem(str: str) -> str:
return re.sub(r'name: "encoding: .*$', '', str, flags=re.MULTILINE)
def count_files_in_dir(path: PathLike) -> int:
return len([name for name in os.listdir(path) if os.path.isfile(os.path.join(path, name))])

@ -1,351 +0,0 @@
#!/bin/bash
# Upgrades Protoc from https://github.com/protocolbuffers/protobuf/releases
black='\e[0;30m'
blackBold='\e[1;30m'
blackBackground='\e[1;40m'
red='\e[0;31m'
redBold='\e[1;31m'
redBackground='\e[0;41m'
green='\e[0;32m'
greenBold='\e[1;32m'
greenBackground='\e[0;42m'
yellow='\e[0;33m'
yellowBold='\e[1;33m'
yellowBackground='\e[0;43m'
blue='\e[0;34m'
blueBold='\e[1;34m'
blueBackground='\e[0;44m'
magenta='\e[0;35m'
magentaBold='\e[1;35m'
magentaBackground='\e[0;45m'
cyan='\e[0;36m'
cyanBold='\e[1;36m'
cyanBackground='\e[0;46m'
white='\e[0;37m'
whiteBold='\e[1;37m'
whiteBackground='\e[0;47m'
reset='\e[0m'
abort() {
echo '
***************
*** ABORTED ***
***************
' >&2
echo "An error occurred on line $1. Exiting..." >&2
date -Iseconds >&2
exit 1
}
trap 'abort $LINENO' ERR
set -e -o pipefail
quit() {
trap : 0
exit 0
}
# Asks if [Yn] if script shoud continue, otherwise exit 1
# $1: msg or nothing
# Example call 1: askContinueYn
# Example call 1: askContinueYn "Backup DB?"
askContinueYn() {
if [[ $1 ]]; then
msg="$1 "
else
msg=""
fi
# http://stackoverflow.com/questions/3231804/in-bash-how-to-add-are-you-sure-y-n-to-any-command-or-alias
read -e -p "${msg}Continue? [Y/n] " response
response=${response,,} # tolower
if [[ $response =~ ^(yes|y|)$ ]] ; then
# echo ""
# OK
:
else
echo "Aborted"
exit 1
fi
}
# Reference: https://gist.github.com/steinwaywhw/a4cd19cda655b8249d908261a62687f8
echo "Checking Protoc version..."
VERSION=$(curl -sL https://github.com/protocolbuffers/protobuf/releases/latest | grep -E "<title>" | perl -pe's%.*Protocol Buffers v(\d+\.\d+(\.\d+)?).*%\1%')
BASEVERSION=4
echo
interactive=true
ignore_version_check=true
clean=false
build_docker=true
while test $# -gt 0; do
case $1 in
-h|--help)
echo "Upgrade Protoc"
echo
echo "$0 [options]"
echo
echo "Options:"
echo "-a Automatic mode"
echo "-C Ignore version check"
echo "-D No docker build"
echo "-c Clean"
echo "-h, --help Help"
quit
;;
-a)
interactive=false
shift
;;
-C)
ignore_version_check=false
shift
;;
-D)
build_docker=false
shift
;;
-c)
clean=true
shift
;;
esac
done
BIN="$HOME/bin"
DOWNLOADS="$HOME/downloads"
PYTHON="python3.11"
PIP="pip3.11"
PIPENV="$PYTHON -m pipenv"
FLAKE8="$PYTHON -m flake8"
MYPY="$PYTHON -m mypy"
# Upgrade protoc
DEST="protoc"
OLDVERSION=$(cat $BIN/$DEST/.VERSION.txt || echo "")
echo -e "\nProtoc remote version $VERSION\n"
echo -e "Protoc local version: $OLDVERSION\n"
if $clean; then
cmd="rm -r dist/ build/ *.whl pytest.xml pytest-coverage.txt .coverage tests/reports || true; find . -name '*.pyc' -type f -delete; find . -name '__pycache__' -type d -exec rm -r {} \; || true; find . -name '*.egg-info' -type d -exec rm -r {} \; || true; find . -name '*_cache' -type d -exec rm -r {} \; || true"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="pipenv --rm || true"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="sudo pipenv --rm || true"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
if [ "$OLDVERSION" != "$VERSION" ] || ! $ignore_version_check; then
echo "Upgrade protoc from $OLDVERSION to $VERSION"
NAME="protoc-$VERSION"
ARCHIVE="$NAME.zip"
mkdir -p $DOWNLOADS
# https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-linux-x86_64.zip
cmd="wget --trust-server-names https://github.com/protocolbuffers/protobuf/releases/download/v$VERSION/protoc-$VERSION-linux-x86_64.zip -O $DOWNLOADS/$ARCHIVE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="echo -e '\nSize [Byte]'; stat --printf='%s\n' $DOWNLOADS/$ARCHIVE; echo -e '\nMD5'; md5sum $DOWNLOADS/$ARCHIVE; echo -e '\nSHA256'; sha256sum $DOWNLOADS/$ARCHIVE;"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="mkdir -p $BIN/$NAME; unzip $DOWNLOADS/$ARCHIVE -d $BIN/$NAME"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="echo $VERSION > $BIN/$NAME/.VERSION.txt; echo $VERSION > $BIN/$NAME/.VERSION_$VERSION.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="[ -d $BIN/$DEST.old ] && rm -rf $BIN/$DEST.old || echo 'No old dir to delete'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="[ -d $BIN/$DEST ] && mv -iT $BIN/$DEST $BIN/$DEST.old || echo 'No previous dir to keep'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="mv -iT $BIN/$NAME $BIN/$DEST"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="rm $DOWNLOADS/$ARCHIVE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$BIN/$DEST/bin/protoc --plugin=protoc-gen-mypy=$HOME/.local/bin/protoc-gen-mypy --python_out=protobuf_generated_python --mypy_out=protobuf_generated_python src/google_auth.proto"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Update README.md
cmd="perl -i -pe 's%proto(buf|c)([- ])(\d\.)?$OLDVERSION%proto\$1\$2\${3}$VERSION%g' README.md && perl -i -pe 's%(protobuf/releases/tag/v)$OLDVERSION%\${1}$VERSION%g' README.md"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
else
echo -e "\nVersion has not changed. Quit"
fi
# Upgrade pip requirements
cmd="sudo pip install --upgrade pip"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
$PIP --version
cmd="$PIP install --use-pep517 -U -r requirements.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PIP install --use-pep517 -U -r requirements-dev.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PIP install -U pipenv"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="sudo $PIP install --use-pep517 -U -r requirements.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="sudo $PIP install --use-pep517 -U -r requirements-dev.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="sudo $PIP install -U pipenv"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
$PIPENV --version
cmd="$PIPENV update && $PIPENV --rm && $PIPENV install"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
$PIPENV run python --version
# Lint
cmd="$FLAKE8 . --count --select=E9,F63,F7,F82 --show-source --statistics"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$FLAKE8 . --count --exit-zero --max-complexity=10 --max-line-length=200 --statistics --exclude=.git,__pycache__,docs/source/conf.py,old,build,dist,protobuf_generated_python"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Type checking
cmd="$MYPY --install-types --non-interactive src/*.py tests/*.py"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# change to src as python -m mypy adds the current dir Python sys.path
# execute in a subshell in order not to loose the exit code and not to change the dir in the currrent shell
cmd="$MYPY --strict src/*.py tests/*.py"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# pip install
cmd="$PIP install -e ."
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="extract_otp_secrets example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="extract_otp_secrets - < example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Test
cmd="$PYTHON src/extract_otp_secrets.py example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PYTHON src/extract_otp_secrets.py - < example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
COVERAGE_OUT="tests/reports/pytest-coverage.txt"
cmd="mkdir -p tests/reports; pytest --cov=extract_otp_secrets_test --junitxml=tests/reports/pytest.xml --cov-report html:tests/reports/html --cov-report=term-missing tests/ | tee $COVERAGE_OUT"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="$PIPENV run pytest --cov=extract_otp_secrets_test tests/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Update Code Coverage in README.md
# https://github.com/marketplace/actions/pytest-coverage-comment
# Coverage-95%25-yellowgreen
echo -e "Update code coverage in README.md"
TOTAL_COVERAGE=$(cat $COVERAGE_OUT | grep 'TOTAL' | perl -ne 'print "$&" if /\b(\d{1,3})%/') && perl -i -pe "s/coverage-(\d{1,3}%)25-/coverage-${TOTAL_COVERAGE}25-/" README.md
if $build_docker; then
# Build docker
cmd="docker build . -t extract_otp_secrets_only_txt -f Dockerfile_only_txt --pull"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker run --rm -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt - < example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt tests/extract_otp_secrets_test.py -k 'not qreader' -vvv --relaxed"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker build . -t extract_otp_secrets --pull"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker run --rm -v \"$(pwd)\":/files:ro extract_otp_secrets example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets - < example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="docker image prune || echo 'No docker image pruning'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
cmd="$PYTHON src/extract_otp_secrets.py &"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
quit
Loading…
Cancel
Save