Compare commits

..

9 Commits

Author SHA1 Message Date
scito
861d7d0da8 build buster image in ci_docker for speeding up releases 2023-01-30 11:28:07 +01:00
scito
63fc21cc90 ci_release: upload hashes and run on pull_request and run scheduled 2023-01-30 11:28:07 +01:00
scito
d234cf4112 cv2: fix save as csv/json/keepass 2023-01-30 11:28:07 +01:00
scito
970dbd3759 cv2: save as csv/json/keppass by key command 2023-01-29 16:50:55 +01:00
scito
197347a3e9 update README 2023-01-27 20:58:33 +01:00
scito
365d5ac432 use hub.docker for linux exe build
since it is more stable and thus faster
2023-01-26 01:14:45 +01:00
scito
88ff584e47 ci: workaround for failing builds 2023-01-26 00:18:41 +01:00
scito
dbb5d8f755 workaround ghcr.io failed to copy
failed to copy: io: read/write on closed pipe

- use DockerHub instead of dhcr.io
- ref: https://github.com/containerd/containerd/issues/7972
2023-01-25 23:46:22 +01:00
scito
580f94256f Add files of Visual C++ 2013 Redistributable Package
- add files of vcredist_x64.exe as binary data to pyzbar
- thus avoid manual installation of vcredist_x64.exe
- test all qr readers in exe smoketests
- improve README
2023-01-25 23:14:38 +01:00
9 changed files with 385 additions and 112 deletions

View File

@@ -50,6 +50,11 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
@@ -87,7 +92,14 @@ jobs:
- name: Image digest - name: Image digest
# TODO upload digests to assets # TODO upload digests to assets
run: | run: |
echo "extract_otp_secrets: ${{ steps.docker_build_qr_reader_latest.outputs.digest }}" echo "extract_otp_secrets digests: ${{ steps.docker_build_qr_reader_latest.outputs.digest }}"
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
with:
name: debian_digests
path: digests.txt
build-and-push-docker-alpine-image: build-and-push-docker-alpine-image:
name: Build Docker Alpine image and push to repositories name: Build Docker Alpine image and push to repositories
@@ -150,8 +162,91 @@ jobs:
build-args: | build-args: |
RUN_TESTS=true RUN_TESTS=true
- name: Image digest
# TODO upload digests to assets
run: |
echo "extract_otp_secrets:only-txt digests: ${{ steps.docker_build_only_txt.outputs.digest }}"
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
with:
name: alpine_digests
path: digests.txt
build-and-push-docker-buster-image:
name: Build Docker Buster image (for PyInstsaller) and push to repositories
# run only when code is compiling and tests are passing
runs-on: ubuntu-latest
# steps to perform in job
steps:
- name: Checkout code
uses: actions/checkout@v3
# avoid building if there are testing errors
- name: Run smoke test
run: |
sudo apt-get install -y libzbar0
python -m pip install --upgrade pip
pip install -U -r requirements-dev.txt
pip install -U .
pytest
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
# setup Docker build action
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_IO_TOKEN }}
- name: "Build image from Buster and push to GitHub Container Registry"
id: docker_build_buster
if: github.ref == 'refs/heads/master'
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64
# relative path to the place where source code with Dockerfile is located
# TODO file:, move to docker/
context: .
file: Dockerfile
# builder: ${{ steps.buildx.outputs.name }}
build-args: |
BASE_IMAGE=python:3.11-slim-buster
# Note: tags has to be all lower-case
pull: true
tags: |
scit0/extract_otp_secrets:buster
push: true
- name: Image digest - name: Image digest
# TODO upload digests to assets # TODO upload digests to assets
run: | run: |
echo "extract_otp_secrets:only-txt: ${{ steps.docker_build_only_txt.outputs.digest }}" echo "extract_otp_secrets digests: ${{ steps.docker_build_qr_reader_latest.outputs.digest }}"
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v3
with:
name: buster_digests
path: digests.txt

View File

@@ -1,10 +1,8 @@
name: release name: release
# https://data-dive.com/multi-os-deployment-in-cloud-using-pyinstaller-and-github-actions # https://data-dive.com/multi-os-deployment-in-cloud-using-pyinstaller-and-github-actions
# https://github.com/actions/create-release (archived)
# https://github.com/actions/upload-artifact # https://github.com/actions/upload-artifact
# https://github.com/actions/download-artifact # https://github.com/actions/download-artifact
# https://github.com/actions/upload-release-asset (archived)
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
# https://github.com/marketplace/actions/generate-release-hashes # https://github.com/marketplace/actions/generate-release-hashes
@@ -36,12 +34,17 @@ on:
push: push:
tags: tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
pull_request:
schedule:
# Run weekly on default branch
- cron: '47 4 * * 6'
jobs: jobs:
create-release: create-release:
name: Create Release name: Create Release
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps: steps:
- name: Set meta data - name: Set meta data
id: meta id: meta
@@ -80,7 +83,7 @@ jobs:
name: release_id name: release_id
path: release_id.txt path: release_id.txt
build-and-push-docker-image: build-linux-executable-in-docker:
name: Build Linux release in docker container name: Build Linux release in docker container
# run only when code is compiling and tests are passing # run only when code is compiling and tests are passing
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -107,6 +110,11 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed
with:
driver-opts: |
image=moby/buildkit:v0.10.6
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v2 uses: docker/login-action@v2
@@ -123,6 +131,8 @@ jobs:
- name: "Build image from Buster and push to GitHub Container Registry" - name: "Build image from Buster and push to GitHub Container Registry"
id: docker_build_buster id: docker_build_buster
# Disable and build in ci_docker for speeding up releases
if: false
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
@@ -136,7 +146,7 @@ jobs:
# Note: tags has to be all lower-case # Note: tags has to be all lower-case
pull: true pull: true
tags: | tags: |
ghcr.io/scito/extract_otp_secrets:buster scit0/extract_otp_secrets:buster
push: true push: true
# # https://stackoverflow.com/a/61155718/1663871 # # https://stackoverflow.com/a/61155718/1663871
@@ -153,7 +163,7 @@ jobs:
- name: Run Pyinstaller in container - name: Run Pyinstaller in container
run: | run: |
# TODO use local docker image https://stackoverflow.com/a/61155718/1663871 # TODO use local docker image https://stackoverflow.com/a/61155718/1663871
docker run --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files ghcr.io/scito/extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U -r /files/requirements.txt && pip install pyinstaller && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py' docker run --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files scit0/extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U -r /files/requirements.txt && pip install pyinstaller && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'
- name: Smoke tests - name: Smoke tests
run: | run: |
@@ -161,7 +171,13 @@ jobs:
dist/extract_otp_secrets_linux_x86_64 -h dist/extract_otp_secrets_linux_x86_64 -h
dist/extract_otp_secrets_linux_x86_64 example_export.png dist/extract_otp_secrets_linux_x86_64 example_export.png
dist/extract_otp_secrets_linux_x86_64 - < example_export.txt dist/extract_otp_secrets_linux_x86_64 - < example_export.txt
dist/extract_otp_secrets_linux_x86_64 --qr ZBAR example_export.png
dist/extract_otp_secrets_linux_x86_64 --qr QREADER example_export.png
dist/extract_otp_secrets_linux_x86_64 --qr QREADER_DEEP example_export.png
dist/extract_otp_secrets_linux_x86_64 --qr CV2 example_export.png
dist/extract_otp_secrets_linux_x86_64 --qr CV2_WECHAT example_export.png
- name: Load Release URL File from release job - name: Load Release URL File from release job
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: release_url name: release_url
@@ -169,7 +185,7 @@ jobs:
run: ls -R run: ls -R
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
# TODO only for tags if: startsWith(github.ref, 'refs/tags/v')
run: | run: |
response=$(curl \ response=$(curl \
-X POST \ -X POST \
@@ -182,8 +198,8 @@ jobs:
--data-binary @dist/extract_otp_secrets_linux_x86_64 \ --data-binary @dist/extract_otp_secrets_linux_x86_64 \
$(cat release_url.txt)=extract_otp_secrets_linux_x86_64) $(cat release_url.txt)=extract_otp_secrets_linux_x86_64)
build: build-native-executables:
name: Build packages name: Build native packages
needs: create-release needs: create-release
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@@ -195,8 +211,9 @@ jobs:
# TODO add --icon # TODO add --icon
# TODO add --manifest # TODO add --manifest
# TODO find more elegant solution for pyzbar\libiconv.dll and pyzbar\libzbar-64.dll # TODO find more elegant solution for pyzbar\libiconv.dll and pyzbar\libzbar-64.dll
# Files of Visual C++ 2013 Redistributable Package: https://support.microsoft.com/en-us/topic/update-for-visual-c-2013-redistributable-package-d8ccd6a5-4e26-c290-517b-8da6cfdf4f10
CMD_BUILD: | CMD_BUILD: |
pyinstaller -y --add-data "$($Env:pythonLocation)\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libiconv.dll;pyzbar" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libzbar-64.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py pyinstaller -y --add-data "$($Env:pythonLocation)\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libiconv.dll;pyzbar" --add-binary "$($Env:pythonLocation)\Lib\site-packages\pyzbar\libzbar-64.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\msvcr120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\msvcp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vcamp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vcomp120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\vccorlib120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120u.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120chs.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120cht.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120deu.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120enu.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120esn.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120fra.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120ita.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120jpn.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120kor.dll;pyzbar" --add-binary "$($Env:WinDir)\system32\mfc120rus.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py
OUT_FILE_NAME: extract_otp_secrets.exe OUT_FILE_NAME: extract_otp_secrets.exe
ASSET_NAME: extract_otp_secrets_win_x86_64.exe ASSET_NAME: extract_otp_secrets_win_x86_64.exe
ASSET_MIME: application/vnd.microsoft.portable-executable ASSET_MIME: application/vnd.microsoft.portable-executable
@@ -224,6 +241,12 @@ jobs:
ASSET_MIME: application/x-executable ASSET_MIME: application/x-executable
UPLOAD: false UPLOAD: false
steps: steps:
- name: Output path
if: runner.os == 'Windows'
run: echo "$($Env:Path)"
- name: List Windir
if: runner.os == 'Windows'
run: ls "$($Env:WinDir)\system32"
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Set macos macos_python_path - name: Set macos macos_python_path
# TODO use variable for Python version # TODO use variable for Python version
@@ -259,15 +282,22 @@ jobs:
dist/${{ matrix.OUT_FILE_NAME }} -V dist/${{ matrix.OUT_FILE_NAME }} -V
dist/${{ matrix.OUT_FILE_NAME }} -h dist/${{ matrix.OUT_FILE_NAME }} -h
dist/${{ matrix.OUT_FILE_NAME }} example_export.png dist/${{ matrix.OUT_FILE_NAME }} example_export.png
dist/${{ matrix.OUT_FILE_NAME }} --qr ZBAR example_export.png
dist/${{ matrix.OUT_FILE_NAME }} --qr QREADER example_export.png
dist/${{ matrix.OUT_FILE_NAME }} --qr QREADER_DEEP example_export.png
dist/${{ matrix.OUT_FILE_NAME }} --qr CV2 example_export.png
dist/${{ matrix.OUT_FILE_NAME }} --qr CV2_WECHAT example_export.png
- name: Smoke tests for generated exe (stdin) - name: Smoke tests for generated exe (stdin)
if: runner.os != 'Windows' if: runner.os != 'Windows'
run: | run: |
dist/${{ matrix.OUT_FILE_NAME }} - < example_export.txt dist/${{ matrix.OUT_FILE_NAME }} - < example_export.txt
- name: Load Release URL File from release job - name: Load Release URL File from release job
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: release_url name: release_url
- name: Load Release Id File from release job - name: Load Release Id File from release job
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v3 uses: actions/download-artifact@v3
with: with:
name: release_id name: release_id
@@ -275,14 +305,66 @@ jobs:
run: ls -R run: ls -R
- name: Set meta data - name: Set meta data
id: meta id: meta
if: startsWith(github.ref, 'refs/tags/v')
shell: bash shell: bash
run: | run: |
cat release_url.txt
echo "release_url=$(cat release_url.txt)" >> $GITHUB_OUTPUT
echo "release_id=$(cat release_id.txt)" >> $GITHUB_OUTPUT echo "release_id=$(cat release_id.txt)" >> $GITHUB_OUTPUT
echo "upload_url=https://uploads.github.com/repos/scito/extract_otp_secrets/releases/$(cat release_id.txt)/assets?name=" >> $GITHUB_OUTPUT echo "upload_url=https://uploads.github.com/repos/scito/extract_otp_secrets/releases/$(cat release_id.txt)/assets?name=" >> $GITHUB_OUTPUT
- name: Upload Release Asset - name: Upload Release Asset
id: upload-release-asset id: upload-release-asset
if: ${{ matrix.UPLOAD }} if: matrix.UPLOAD && startsWith(github.ref, 'refs/tags/v')
run: | run: |
curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: ${{ matrix.ASSET_MIME }}" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data-binary @dist/${{ matrix.OUT_FILE_NAME }} ${{ steps.meta.outputs.upload_url }}=${{ matrix.ASSET_NAME }} curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: ${{ matrix.ASSET_MIME }}" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data-binary @dist/${{ matrix.OUT_FILE_NAME }} ${{ steps.meta.outputs.upload_url }}=${{ matrix.ASSET_NAME }}
upload-hashes:
name: Upload hashes
if: startsWith(github.ref, 'refs/tags/v')
needs:
- build-linux-executable-in-docker
- build-native-executables
runs-on: ubuntu-latest
steps:
- name: Load Release Id File from release job
uses: actions/download-artifact@v3
with:
name: release_id
- name: Set meta data
id: meta
run: |
echo "release_id=$(cat release_id.txt)" >> $GITHUB_OUTPUT
echo "upload_url=https://uploads.github.com/repos/scito/extract_otp_secrets/releases/$(cat release_id.txt)/assets?name=" >> $GITHUB_OUTPUT
- name: Calculate and upload hashes from assets
run: |
GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}
for asset_url in $(curl \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN"\
-H "X-GitHub-Api-Version: 2022-11-28" \
--silent \
--show-error \
https://api.github.com/repos/scito/extract_otp_secrets/releases/${{ steps.meta.outputs.release_id }}/assets |
jq -r '.[].url'); do
echo "Download $asset_url"
name=$(curl \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN"\
-H "X-GitHub-Api-Version: 2022-11-28" \
--output-dir assets \
-L \
$asset_url |
jq -r '.name')
curl \
-H "Accept: application/octet-stream" \
-H "Authorization: Bearer $GITHUB_TOKEN"\
-H "X-GitHub-Api-Version: 2022-11-28" \
--create-dirs \
--output-dir assets \
-L \
-o $name \
$asset_url
done
(cd assets/ && sha256sum * > ../sha256_hashes.txt)
curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: text/plain" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data @sha256_hashes.txt ${{ steps.meta.outputs.upload_url }}=sha256_hashes.txt
(cd assets/ && sha512sum * > ../sha512_hashes.txt)
curl -X POST -H "Accept: application/vnd.github+json" -H "Content-Type: text/plain" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "X-GitHub-Api-Version: 2022-11-28" --show-error --data @sha512_hashes.txt ${{ steps.meta.outputs.upload_url }}=sha512_hashes.txt

1
.gitignore vendored
View File

@@ -25,3 +25,4 @@ dist_*/
file_version_info_python.txt file_version_info_python.txt
file_version_info_explorer.txt file_version_info_explorer.txt
file_version_info.txt file_version_info.txt
assets/*

View File

@@ -21,6 +21,7 @@ RUN apt-get update && apt-get install -y \
libglib2.0-0 \ libglib2.0-0 \
libsm6 \ libsm6 \
libzbar0 \ libzbar0 \
python3-tk \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U -r requirements.txt \ && pip install --no-cache-dir -U -r requirements.txt \
&& if [ "$RUN_TESTS" = "true" ]; then /extract/run_pytest.sh; else echo "Not running tests..."; fi \ && if [ "$RUN_TESTS" = "true" ]; then /extract/run_pytest.sh; else echo "Not running tests..."; fi \

View File

@@ -9,6 +9,7 @@ opencv-contrib-python = "*"
# for macOS: opencv-contrib-python = "<=4.7.0" # for macOS: opencv-contrib-python = "<=4.7.0"
# for PYTHON <= 3.7: typing_extensions = "*" # for PYTHON <= 3.7: typing_extensions = "*"
pillow = "*" pillow = "*"
pyzbar = "*"
protobuf = "*" protobuf = "*"
qrcode = "*" qrcode = "*"
qreader = "<2.0.0" qreader = "<2.0.0"

121
Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "41edd4aebe075d6c39d035ec7cb10f0253a3ad21f9b4aa5b9c57deccca87874f" "sha256": "42b14c5eae25b0924354520fe0a26a8d826c905f4613d717f3bfa52e98ed5e8e"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -200,6 +200,7 @@
"sha256:4559628b8192feb25766d954b36a3753baaf5c97c03135aec7e4a026036b475d", "sha256:4559628b8192feb25766d954b36a3753baaf5c97c03135aec7e4a026036b475d",
"sha256:8f4c5264c9c7c6b9f20d01efc52a4eba1ded47d9ba857a94130afe33703eb518" "sha256:8f4c5264c9c7c6b9f20d01efc52a4eba1ded47d9ba857a94130afe33703eb518"
], ],
"index": "pypi",
"version": "==0.1.9" "version": "==0.1.9"
}, },
"qrcode": { "qrcode": {
@@ -247,60 +248,60 @@
"toml" "toml"
], ],
"hashes": [ "hashes": [
"sha256:051afcbd6d2ac39298d62d340f94dbb6a1f31de06dfaf6fcef7b759dd3860c45", "sha256:04481245ef966fbd24ae9b9e537ce899ae584d521dfbe78f89cad003c38ca2ab",
"sha256:0a1890fca2962c4f1ad16551d660b46ea77291fba2cc21c024cd527b9d9c8809", "sha256:0c45948f613d5d18c9ec5eaa203ce06a653334cf1bd47c783a12d0dd4fd9c851",
"sha256:0ee30375b409d9a7ea0f30c50645d436b6f5dfee254edffd27e45a980ad2c7f4", "sha256:10188fe543560ec4874f974b5305cd1a8bdcfa885ee00ea3a03733464c4ca265",
"sha256:13250b1f0bd023e0c9f11838bdeb60214dd5b6aaf8e8d2f110c7e232a1bff83b", "sha256:218fe982371ac7387304153ecd51205f14e9d731b34fb0568181abaf7b443ba0",
"sha256:17e01dd8666c445025c29684d4aabf5a90dc6ef1ab25328aa52bedaa95b65ad7", "sha256:29571503c37f2ef2138a306d23e7270687c0efb9cab4bd8038d609b5c2393a3a",
"sha256:19245c249aa711d954623d94f23cc94c0fd65865661f20b7781210cb97c471c0", "sha256:2a60d6513781e87047c3e630b33b4d1e89f39836dac6e069ffee28c4786715f5",
"sha256:1caed2367b32cc80a2b7f58a9f46658218a19c6cfe5bc234021966dc3daa01f0", "sha256:2bf1d5f2084c3932b56b962a683074a3692bce7cabd3aa023c987a2a8e7612f6",
"sha256:1f66862d3a41674ebd8d1a7b6f5387fe5ce353f8719040a986551a545d7d83ea", "sha256:3164d31078fa9efe406e198aecd2a02d32a62fecbdef74f76dad6a46c7e48311",
"sha256:220e3fa77d14c8a507b2d951e463b57a1f7810a6443a26f9b7591ef39047b1b2", "sha256:32df215215f3af2c1617a55dbdfb403b772d463d54d219985ac7cd3bf124cada",
"sha256:276f4cd0001cd83b00817c8db76730938b1ee40f4993b6a905f40a7278103b3a", "sha256:33d1ae9d4079e05ac4cc1ef9e20c648f5afabf1a92adfaf2ccf509c50b85717f",
"sha256:29de916ba1099ba2aab76aca101580006adfac5646de9b7c010a0f13867cba45", "sha256:33ff26d0f6cc3ca8de13d14fde1ff8efe1456b53e3f0273e63cc8b3c84a063d8",
"sha256:2a7f23bbaeb2a87f90f607730b45564076d870f1fb07b9318d0c21f36871932b", "sha256:38da2db80cc505a611938d8624801158e409928b136c8916cd2e203970dde4dc",
"sha256:2c407b1950b2d2ffa091f4e225ca19a66a9bd81222f27c56bd12658fc5ca1209", "sha256:3b155caf3760408d1cb903b21e6a97ad4e2bdad43cbc265e3ce0afb8e0057e73",
"sha256:30b5fec1d34cc932c1bc04017b538ce16bf84e239378b8f75220478645d11fca", "sha256:3b946bbcd5a8231383450b195cfb58cb01cbe7f8949f5758566b881df4b33baf",
"sha256:3c2155943896ac78b9b0fd910fb381186d0c345911f5333ee46ac44c8f0e43ab", "sha256:3baf5f126f30781b5e93dbefcc8271cb2491647f8283f20ac54d12161dff080e",
"sha256:411d4ff9d041be08fdfc02adf62e89c735b9468f6d8f6427f8a14b6bb0a85095", "sha256:4b14d5e09c656de5038a3f9bfe5228f53439282abcab87317c9f7f1acb280352",
"sha256:436e103950d05b7d7f55e39beeb4d5be298ca3e119e0589c0227e6d0b01ee8c7", "sha256:51b236e764840a6df0661b67e50697aaa0e7d4124ca95e5058fa3d7cbc240b7c",
"sha256:49640bda9bda35b057b0e65b7c43ba706fa2335c9a9896652aebe0fa399e80e6", "sha256:63ffd21aa133ff48c4dff7adcc46b7ec8b565491bfc371212122dd999812ea1c",
"sha256:4a950f83fd3f9bca23b77442f3a2b2ea4ac900944d8af9993743774c4fdc57af", "sha256:6a43c7823cd7427b4ed763aa7fb63901ca8288591323b58c9cd6ec31ad910f3c",
"sha256:50a6adc2be8edd7ee67d1abc3cd20678987c7b9d79cd265de55941e3d0d56499", "sha256:755e89e32376c850f826c425ece2c35a4fc266c081490eb0a841e7c1cb0d3bda",
"sha256:52ab14b9e09ce052237dfe12d6892dd39b0401690856bcfe75d5baba4bfe2831", "sha256:7a726d742816cb3a8973c8c9a97539c734b3a309345236cd533c4883dda05b8d",
"sha256:54f7e9705e14b2c9f6abdeb127c390f679f6dbe64ba732788d3015f7f76ef637", "sha256:7c7c0d0827e853315c9bbd43c1162c006dd808dbbe297db7ae66cd17b07830f0",
"sha256:66e50680e888840c0995f2ad766e726ce71ca682e3c5f4eee82272c7671d38a2", "sha256:7ed681b0f8e8bcbbffa58ba26fcf5dbc8f79e7997595bf071ed5430d8c08d6f3",
"sha256:790e4433962c9f454e213b21b0fd4b42310ade9c077e8edcb5113db0818450cb", "sha256:7ee5c9bb51695f80878faaa5598040dd6c9e172ddcf490382e8aedb8ec3fec8d",
"sha256:7a38362528a9115a4e276e65eeabf67dcfaf57698e17ae388599568a78dcb029", "sha256:8361be1c2c073919500b6601220a6f2f98ea0b6d2fec5014c1d9cfa23dd07038",
"sha256:7b05ed4b35bf6ee790832f68932baf1f00caa32283d66cc4d455c9e9d115aafc", "sha256:8ae125d1134bf236acba8b83e74c603d1b30e207266121e76484562bc816344c",
"sha256:7e109f1c9a3ece676597831874126555997c48f62bddbcace6ed17be3e372de8", "sha256:9817733f0d3ea91bea80de0f79ef971ae94f81ca52f9b66500c6a2fea8e4b4f8",
"sha256:949844af60ee96a376aac1ded2a27e134b8c8d35cc006a52903fc06c24a3296f", "sha256:98b85dd86514d889a2e3dd22ab3c18c9d0019e696478391d86708b805f4ea0fa",
"sha256:95304068686545aa368b35dfda1cdfbbdbe2f6fe43de4a2e9baa8ebd71be46e2", "sha256:9ccb092c9ede70b2517a57382a601619d20981f56f440eae7e4d7eaafd1d1d09",
"sha256:9e662e6fc4f513b79da5d10a23edd2b87685815b337b1a30cd11307a6679148d", "sha256:9d58885215094ab4a86a6aef044e42994a2bd76a446dc59b352622655ba6621b",
"sha256:a9fed35ca8c6e946e877893bbac022e8563b94404a605af1d1e6accc7eb73289", "sha256:b643cb30821e7570c0aaf54feaf0bfb630b79059f85741843e9dc23f33aaca2c",
"sha256:b69522b168a6b64edf0c33ba53eac491c0a8f5cc94fa4337f9c6f4c8f2f5296c", "sha256:bc7c85a150501286f8b56bd8ed3aa4093f4b88fb68c0843d21ff9656f0009d6a",
"sha256:b78729038abea6a5df0d2708dce21e82073463b2d79d10884d7d591e0f385ded", "sha256:beeb129cacea34490ffd4d6153af70509aa3cda20fdda2ea1a2be870dfec8d52",
"sha256:b8c56bec53d6e3154eaff6ea941226e7bd7cc0d99f9b3756c2520fc7a94e6d96", "sha256:c31b75ae466c053a98bf26843563b3b3517b8f37da4d47b1c582fdc703112bc3",
"sha256:b9727ac4f5cf2cbf87880a63870b5b9730a8ae3a4a360241a0fdaa2f71240ff0", "sha256:c4e4881fa9e9667afcc742f0c244d9364d197490fbc91d12ac3b5de0bf2df146",
"sha256:ba3027deb7abf02859aca49c865ece538aee56dcb4871b4cced23ba4d5088904", "sha256:c5b15ed7644ae4bee0ecf74fee95808dcc34ba6ace87e8dfbf5cb0dc20eab45a",
"sha256:be9fcf32c010da0ba40bf4ee01889d6c737658f4ddff160bd7eb9cac8f094b21", "sha256:d12d076582507ea460ea2a89a8c85cb558f83406c8a41dd641d7be9a32e1274f",
"sha256:c18d47f314b950dbf24a41787ced1474e01ca816011925976d90a88b27c22b89", "sha256:d248cd4a92065a4d4543b8331660121b31c4148dd00a691bfb7a5cdc7483cfa4",
"sha256:c76a3075e96b9c9ff00df8b5f7f560f5634dffd1658bafb79eb2682867e94f78", "sha256:d47dd659a4ee952e90dc56c97d78132573dc5c7b09d61b416a9deef4ebe01a0c",
"sha256:cbfcba14a3225b055a28b3199c3d81cd0ab37d2353ffd7f6fd64844cebab31ad", "sha256:d4a5a5879a939cb84959d86869132b00176197ca561c664fc21478c1eee60d75",
"sha256:d254666d29540a72d17cc0175746cfb03d5123db33e67d1020e42dae611dc196", "sha256:da9b41d4539eefd408c46725fb76ecba3a50a3367cafb7dea5f250d0653c1040",
"sha256:d66187792bfe56f8c18ba986a0e4ae44856b1c645336bd2c776e3386da91e1dd", "sha256:db61a79c07331e88b9a9974815c075fbd812bc9dbc4dc44b366b5368a2936063",
"sha256:d8d04e755934195bdc1db45ba9e040b8d20d046d04d6d77e71b3b34a8cc002d0", "sha256:ddb726cb861c3117a553f940372a495fe1078249ff5f8a5478c0576c7be12050",
"sha256:d8f3e2e0a1d6777e58e834fd5a04657f66affa615dae61dd67c35d1568c38882", "sha256:ded59300d6330be27bc6cf0b74b89ada58069ced87c48eaf9344e5e84b0072f7",
"sha256:e057e74e53db78122a3979f908973e171909a58ac20df05c33998d52e6d35757", "sha256:e2617759031dae1bf183c16cef8fcfb3de7617f394c813fa5e8e46e9b82d4222",
"sha256:e4ce984133b888cc3a46867c8b4372c7dee9cee300335e2925e197bcd45b9e16", "sha256:e5cdbb5cafcedea04924568d990e20ce7f1945a1dd54b560f879ee2d57226912",
"sha256:ea76dbcad0b7b0deb265d8c36e0801abcddf6cc1395940a24e3595288b405ca0", "sha256:ec8e767f13be637d056f7e07e61d089e555f719b387a7070154ad80a0ff31801",
"sha256:ecb0f73954892f98611e183f50acdc9e21a4653f294dfbe079da73c6378a6f47", "sha256:ef382417db92ba23dfb5864a3fc9be27ea4894e86620d342a116b243ade5d35d",
"sha256:ef14d75d86f104f03dea66c13188487151760ef25dd6b2dbd541885185f05f40", "sha256:f2cba5c6db29ce991029b5e4ac51eb36774458f0a3b8d3137241b32d1bb91f06",
"sha256:f26648e1b3b03b6022b48a9b910d0ae209e2d51f50441db5dce5b530fad6d9b1", "sha256:f5b4198d85a3755d27e64c52f8c95d6333119e49fd001ae5798dac872c95e0f8",
"sha256:f67472c09a0c7486e27f3275f617c964d25e35727af952869dd496b9b5b7f6a3" "sha256:ffeeb38ee4a80a30a6877c5c4c359e5498eec095878f1581453202bfacc8fbc2"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==7.0.5" "version": "==7.1.0"
}, },
"dill": { "dill": {
"hashes": [ "hashes": [
@@ -336,11 +337,11 @@
}, },
"isort": { "isort": {
"hashes": [ "hashes": [
"sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6", "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504",
"sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b" "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"
], ],
"markers": "python_full_version >= '3.7.0'", "markers": "python_full_version >= '3.8.0'",
"version": "==5.11.4" "version": "==5.12.0"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
"hashes": [ "hashes": [
@@ -545,11 +546,11 @@
}, },
"setuptools": { "setuptools": {
"hashes": [ "hashes": [
"sha256:6f590d76b713d5de4e49fe4fbca24474469f53c83632d5d0fd056f7ff7e8112b", "sha256:883131c5b6efa70b9101c7ef30b2b7b780a4283d5fc1616383cdf22c83cbefe6",
"sha256:ac4008d396bc9cd983ea483cb7139c0240a07bbc74ffb6232fceffedc6cf03a8" "sha256:9d790961ba6219e9ff7d9557622d2fe136816a264dd01d5997cfc057d804853d"
], ],
"markers": "python_version >= '3.7'", "markers": "python_version >= '3.7'",
"version": "==66.1.1" "version": "==67.0.0"
}, },
"setuptools-git-versioning": { "setuptools-git-versioning": {
"hashes": [ "hashes": [

View File

@@ -3,19 +3,20 @@
[![CI tests](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml) [![CI tests](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci.yml)
[![CI docker](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml) [![CI docker](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml/badge.svg)](https://github.com/scito/extract_otp_secrets/actions/workflows/ci_docker.yml)
![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen) ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
<!-- ![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_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
![protobuf version](https://img.shields.io/badge/protobuf-4.21.12-informational)-->
![python versions](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue)
[![License](https://img.shields.io/github/license/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/LICENSE) [![License](https://img.shields.io/github/license/scito/extract_otp_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/LICENSE)
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/scito/extract_otp_secrets?sort=semver)](https://github.com/scito/extract_otp_secrets/releases/latest) [![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/scito/extract_otp_secrets?sort=semver)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![Download executable](https://img.shields.io/badge/download-exe-blue)](https://github.com/scito/extract_otp_secrets/releases/latest) ![python versions](https://img.shields.io/badge/python-3.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue)
[![Docker image](https://img.shields.io/badge/docker-image-blue)](https://hub.docker.com/repository/docker/scit0/extract_otp_secrets/general)
[![Linux](https://img.shields.io/badge/os-linux-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest) [![Linux](https://img.shields.io/badge/os-linux-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![Windows](https://img.shields.io/badge/os-windows-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest) [![Windows](https://img.shields.io/badge/os-windows-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![MacOS](https://img.shields.io/badge/os-macos-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest) [![MacOS](https://img.shields.io/badge/os-macos-yellow)](https://github.com/scito/extract_otp_secrets/releases/latest)
[![Docker image](https://img.shields.io/badge/docker-image-blue)](https://hub.docker.com/repository/docker/scit0/extract_otp_secrets/general) [![Download executable](https://img.shields.io/badge/download-exe-blue)](https://github.com/scito/extract_otp_secrets/releases/latest)
<!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua) [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)
<!-- ![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_secrets)](https://github.com/scito/extract_otp_secrets/blob/master/Pipfile.lock)
![protobuf version](https://img.shields.io/badge/protobuf-4.21.12-informational)-->
<!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
--- ---
@@ -90,9 +91,20 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
1. Download executable for your platform from [latest release](https://github.com/scito/extract_otp_secrets/releases/latest), see assets 1. Download executable for your platform from [latest release](https://github.com/scito/extract_otp_secrets/releases/latest), see assets
2. Start executable by clicking or from command line 2. Start executable by clicking or from command line
Everything is just packed in one executable. :heavy_check_mark: Everything is just packed in one executable.
No installation needed, neither Python nor dependencies have to be installed. :heavy_check_mark: No installation needed, neither Python nor any dependencies have to be installed.
Easy and convenient :heavy_check_mark: Easy and convenient
> :information_source: There is a delay after starting the executable since the files have internally to be unpacked.
> :information_source: If you are a developer, you might prefer to run the Python script directly, see [Installation](#installation-of-python-script-recommend-for-developers-or-advanced-users)
> :warning: Some antivirus tools may show a virus or trojan alert for the executable.
> This alert is a false positive.
> This is a known problem for executables generated by PyInstaller.
> If you have any doubt, please use directly the [Python script](#installation-of-python-script-recommend-for-developers-or-advanced-users).
> :information_source: The executables are not signed. Thus, the operating system may show a warning about download from unknown source.
## Usage ## Usage
@@ -114,6 +126,14 @@ Detected QR codes are surrounded with a frame. The color of the frame indicates
* Red: The QR code is detected and decoded, but could not be successfully extracted. This is the case if a QR code not containing OTP data is captured. * Red: The QR code is detected and decoded, but could not be successfully extracted. This is the case if a QR code not containing OTP data is captured.
* Magenta: The QR code is detected, but could not be decoded. The QR code should be presented better to the camera or another QR reader could be used. * Magenta: The QR code is detected, but could not be decoded. The QR code should be presented better to the camera or another QR reader could be used.
Key commands:
* Space: change QR code reader
* C: save as csv file (🆕 since v2.2)
* J: save as json file (🆕 since v2.2)
* K: save as KeePass csv file (🆕 since v2.2)
* ESC, ENTER, Q: quit the program
The secrets are printed by default to the console. [Set program parameters](#program-help-arguments-and-options) for other types of output, e.g. `--csv exported_secrets.csv`. The secrets are printed by default to the console. [Set program parameters](#program-help-arguments-and-options) for other types of output, e.g. `--csv exported_secrets.csv`.
### With builtin QR decoder from image files (🆕 since version 2.0) ### With builtin QR decoder from image files (🆕 since version 2.0)
@@ -280,6 +300,10 @@ python extract_otp_secrets.py = < example_export.png</pre>
* QREADER_DEEP: [QReader](https://github.com/Eric-Canas/QReader) - very slow in GUI * QREADER_DEEP: [QReader](https://github.com/Eric-Canas/QReader) - very slow in GUI
* CV2: [QRCodeDetector](https://docs.opencv.org/4.x/de/dc3/classcv_1_1QRCodeDetector.html) * CV2: [QRCodeDetector](https://docs.opencv.org/4.x/de/dc3/classcv_1_1QRCodeDetector.html)
* CV2_WECHAT: [WeChatQRCode](https://docs.opencv.org/4.x/dd/d63/group__wechat__qrcode.html) * CV2_WECHAT: [WeChatQRCode](https://docs.opencv.org/4.x/dd/d63/group__wechat__qrcode.html)
* Program usable as pure GUI application without any command line switches (🆕 since v2.2)
* Save otp secrets as csv file (🆕 since v2.2)
* Save otp secrets as json file (🆕 since v2.2)
* Save otp secrets as KeePass csv file(s) (🆕 since v2.2)
* Supports [TOTP](https://www.ietf.org/rfc/rfc6238.txt) and [HOTP](https://www.ietf.org/rfc/rfc4226.txt) standards * Supports [TOTP](https://www.ietf.org/rfc/rfc6238.txt) and [HOTP](https://www.ietf.org/rfc/rfc4226.txt) standards
* Generates QR codes * Generates QR codes
* Exports to various formats: * Exports to various formats:
@@ -611,7 +635,7 @@ Output is executable `dist/extract_otp_secrets`
#### Windows #### Windows
``` ```
pyinstaller -y --add-data "%pythonLocation%\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "%pythonLocation%\pyzbar\libiconv.dll;pyzbar" --add-binary "%pythonLocation%\pyzbar\libzbar-64.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py pyinstaller -y --add-data "%pythonLocation%\__yolo_v3_qr_detector;__yolo_v3_qr_detector" --add-binary "%pythonLocation%\pyzbar\libiconv.dll;pyzbar" --add-binary "%pythonLocation%\pyzbar\libzbar-64.dll;pyzbar" --add-binary "%windir%\system32\msvcr120.dll;pyzbar" --add-binary "%windir%\system32\msvcp120.dll;pyzbar" --add-binary "%windir%\system32\vcamp120.dll;pyzbar" --add-binary "%windir%\system32\vcomp120.dll;pyzbar" --add-binary "%windir%\system32\vccorlib120.dll;pyzbar" --add-binary "%windir%\system32\mfc120.dll;pyzbar" --add-binary "%windir%\system32\mfc120u.dll;pyzbar" --add-binary "%windir%\system32\mfc120chs.dll;pyzbar" --add-binary "%windir%\system32\mfc120cht.dll;pyzbar" --add-binary "%windir%\system32\mfc120deu.dll;pyzbar" --add-binary "%windir%\system32\mfc120enu.dll;pyzbar" --add-binary "%windir%\system32\mfc120esn.dll;pyzbar" --add-binary "%windir%\system32\mfc120fra.dll;pyzbar" --add-binary "%windir%\system32\mfc120ita.dll;pyzbar" --add-binary "%windir%\system32\mfc120jpn.dll;pyzbar" --add-binary "%windir%\system32\mfc120kor.dll;pyzbar" --add-binary "%windir%\system32\mfc120rus.dll;pyzbar" --onefile --version-file build\file_version_info.txt src\extract_otp_secrets.py
``` ```
Output is `dist\extract_otp_secrets.exe` Output is `dist\extract_otp_secrets.exe`

View File

@@ -1,4 +1,5 @@
colorama>=0.4.6 colorama>=0.4.6
importlib_metadata; python_version<='3.7'
opencv-contrib-python; sys_platform != 'darwin' opencv-contrib-python; sys_platform != 'darwin'
opencv-contrib-python<=4.7.0; sys_platform == 'darwin' opencv-contrib-python<=4.7.0; sys_platform == 'darwin'
Pillow Pillow
@@ -7,4 +8,3 @@ pyzbar
qrcode qrcode
qreader<2.0.0 qreader<2.0.0
typing_extensions; python_version<='3.7' typing_extensions; python_version<='3.7'
importlib_metadata; python_version<='3.7'

View File

@@ -65,11 +65,20 @@ else:
debug_mode = '-d' in sys.argv[1:] or '--debug' in sys.argv[1:] debug_mode = '-d' in sys.argv[1:] or '--debug' in sys.argv[1:]
headless: bool = False
try: try:
import cv2 # type: ignore # TODO use cv2 types if available import cv2 # type: ignore # TODO use cv2 types if available
import numpy as np # TODO use numpy types if available import numpy as np # TODO use numpy types if available
try:
import tkinter
import tkinter.filedialog
import tkinter.messagebox
except ImportError:
headless = True
try: try:
import pyzbar.pyzbar as zbar # type: ignore import pyzbar.pyzbar as zbar # type: ignore
from qreader import QReader # type: ignore from qreader import QReader # type: ignore
@@ -143,6 +152,7 @@ quiet: bool = False
colored: bool = True colored: bool = True
executable: bool = False executable: bool = False
__version__: str __version__: str
tk_root: tkinter.Tk
def sys_main() -> None: def sys_main() -> None:
@@ -150,7 +160,7 @@ def sys_main() -> None:
def main(sys_args: list[str]) -> None: def main(sys_args: list[str]) -> None:
global executable global executable, tk_root, headless
# allow to use sys.stdout with with (avoid closing) # allow to use sys.stdout with with (avoid closing)
sys.stdout.close = lambda: None # type: ignore sys.stdout.close = lambda: None # type: ignore
# set encoding to utf-8, needed for Windows # set encoding to utf-8, needed for Windows
@@ -164,6 +174,13 @@ def main(sys_args: list[str]) -> None:
# https://pyinstaller.org/en/stable/runtime-information.html#run-time-information # https://pyinstaller.org/en/stable/runtime-information.html#run-time-information
executable = getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS') executable = getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')
if qreader_available and not headless:
try:
tk_root = tkinter.Tk()
tk_root.withdraw()
except tkinter.TclError:
headless = True
args = parse_args(sys_args) args = parse_args(sys_args)
if colored: if colored:
@@ -174,9 +191,10 @@ def main(sys_args: list[str]) -> None:
sys.exit(0 if do_debug_checks() else 1) sys.exit(0 if do_debug_checks() else 1)
otps = extract_otps(args) otps = extract_otps(args)
write_csv(args, otps)
write_keepass_csv(args, otps) write_csv(args.csv, otps)
write_json(args, otps) write_keepass_csv(args.keepass, otps)
write_json(args.json, otps)
# workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None # workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None
@@ -362,15 +380,16 @@ def extract_otps_from_camera(args: Args) -> Otps:
qr_mode = next_qr_mode(qr_mode) qr_mode = next_qr_mode(qr_mode)
continue continue
cv2_print_text(img, f"Mode: {qr_mode.name} (Hit space to change)", 0, TextPosition.LEFT, FONT_COLOR, 20) cv2_print_text(img, f"Mode: {qr_mode.name} (Hit SPACE to change)", 0, TextPosition.LEFT, FONT_COLOR, 20)
cv2_print_text(img, "Hit ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17) cv2_print_text(img, "Press ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17)
cv2_print_text(img, "Press C/J/K to save as csv/json/keepass file", 2, TextPosition.LEFT, FONT_COLOR, None)
cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR) cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR)
cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR) cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR)
cv2.imshow(WINDOW_NAME, img) cv2.imshow(WINDOW_NAME, img)
quit, qr_mode = cv2_handle_pressed_keys(qr_mode) quit, qr_mode = cv2_handle_pressed_keys(qr_mode, otps)
if quit: if quit:
break break
@@ -416,12 +435,48 @@ def cv2_print_text(img: Any, text: str, line_number: int, position: TextPosition
cv2.putText(img, out_text, pos, FONT, FONT_SCALE, color, FONT_THICKNESS, FONT_LINE_STYLE) cv2.putText(img, out_text, pos, FONT, FONT_SCALE, color, FONT_THICKNESS, FONT_LINE_STYLE)
def cv2_handle_pressed_keys(qr_mode: QRMode) -> Tuple[bool, QRMode]: def cv2_handle_pressed_keys(qr_mode: QRMode, otps: Otps) -> Tuple[bool, QRMode]:
key = cv2.waitKey(1) & 0xFF key = cv2.waitKey(1) & 0xFF
quit = False quit = False
if key == 27 or key == ord('q') or key == 13: if key == 27 or key == ord('q') or key == ord('Q') or key == 13:
# ESC or Enter or q pressed # ESC or Enter or q pressed
quit = True quit = True
elif (key == ord('c') or key == ord('C')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as CSV",
defaultextension='.csv',
filetypes=[('CSV', '*.csv'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_csv(file_name, otps)
elif (key == ord('j') or key == ord('J')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as JSON",
defaultextension='.json',
filetypes=[('JSON', '*.json'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_json(file_name, otps)
elif (key == ord('k') or key == ord('K')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as KeePass CSV file(s)",
defaultextension='.csv',
filetypes=[('CSV', '*.csv'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_keepass_csv(file_name, otps)
elif key == 32: elif key == 32:
qr_mode = next_qr_mode(qr_mode) qr_mode = next_qr_mode(qr_mode)
if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}") if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}")
@@ -626,22 +681,22 @@ def print_qr(args: Args, otp_url: str) -> None:
qr.print_ascii() qr.print_ascii()
def write_csv(args: Args, otps: Otps) -> None: def write_csv(file: str, otps: Otps) -> None:
if args.csv and len(otps) > 0: if file and len(file) > 0 and len(otps) > 0:
with open_file_or_stdout_for_csv(args.csv) as outfile: with open_file_or_stdout_for_csv(file) as outfile:
writer = csv.DictWriter(outfile, otps[0].keys()) writer = csv.DictWriter(outfile, otps[0].keys())
writer.writeheader() writer.writeheader()
writer.writerows(otps) writer.writerows(otps)
if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to csv {args.csv}") if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to csv {file}")
def write_keepass_csv(args: Args, otps: Otps) -> None: def write_keepass_csv(file: str, otps: Otps) -> None:
if args.keepass and len(otps) > 0: if file and len(file) > 0 and len(otps) > 0:
has_totp = has_otp_type(otps, 'totp') has_totp = has_otp_type(otps, 'totp')
has_hotp = has_otp_type(otps, 'hotp') has_hotp = has_otp_type(otps, 'hotp')
if args.keepass != '-': if file != '-':
otp_filename_totp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "totp") otp_filename_totp = file if has_totp != has_hotp else add_pre_suffix(file, "totp")
otp_filename_hotp = args.keepass if has_totp != has_hotp else add_pre_suffix(args.keepass, "hotp") otp_filename_hotp = file if has_totp != has_hotp else add_pre_suffix(file, "hotp")
else: else:
otp_filename_totp = otp_filename_hotp = '-' otp_filename_totp = otp_filename_hotp = '-'
if has_totp: if has_totp:
@@ -653,9 +708,9 @@ def write_keepass_csv(args: Args, otps: Otps) -> None:
if count_hotp_entries: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}") if count_hotp_entries: print(f"Exported {count_hotp_entries} hotp entrie{'s'[:count_hotp_entries != 1]} to keepass csv file {otp_filename_hotp}")
def write_keepass_totp_csv(otp_filename: str, otps: Otps) -> int: def write_keepass_totp_csv(file: str, otps: Otps) -> int:
count_entries = 0 count_entries = 0
with open_file_or_stdout_for_csv(otp_filename) as outfile: with open_file_or_stdout_for_csv(file) as outfile:
writer = csv.DictWriter(outfile, ["Title", "User Name", "TimeOtp-Secret-Base32", "Group"]) writer = csv.DictWriter(outfile, ["Title", "User Name", "TimeOtp-Secret-Base32", "Group"])
writer.writeheader() writer.writeheader()
for otp in otps: for otp in otps:
@@ -670,9 +725,9 @@ def write_keepass_totp_csv(otp_filename: str, otps: Otps) -> int:
return count_entries return count_entries
def write_keepass_htop_csv(otp_filename: str, otps: Otps) -> int: def write_keepass_htop_csv(file: str, otps: Otps) -> int:
count_entries = 0 count_entries = 0
with open_file_or_stdout_for_csv(otp_filename) as outfile: with open_file_or_stdout_for_csv(file) as outfile:
writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"]) writer = csv.DictWriter(outfile, ["Title", "User Name", "HmacOtp-Secret-Base32", "HmacOtp-Counter", "Group"])
writer.writeheader() writer.writeheader()
for otp in otps: for otp in otps:
@@ -688,11 +743,11 @@ def write_keepass_htop_csv(otp_filename: str, otps: Otps) -> int:
return count_entries return count_entries
def write_json(args: Args, otps: Otps) -> None: def write_json(file: str, otps: Otps) -> None:
if args.json: if file and len(file) > 0:
with open_file_or_stdout(args.json) as outfile: with open_file_or_stdout(file) as outfile:
json.dump(otps, outfile, indent=4) json.dump(otps, outfile, indent=4)
if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to json {args.json}") if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to json {file}")
def has_otp_type(otps: Otps, otp_type: str) -> bool: def has_otp_type(otps: Otps, otp_type: str) -> bool:
@@ -729,6 +784,13 @@ def check_file_exists(filename: str) -> None:
f"\ninput file: {filename}") f"\ninput file: {filename}")
def has_no_otps_show_warning(otps: Otps) -> bool:
if len(otps) == 0:
tkinter.messagebox.showinfo(title="No data", message="There are no otp secrets to write")
tk_root.update() # dispose dialog
return len(otps) == 0
def is_binary(line: str) -> bool: def is_binary(line: str) -> bool:
try: try:
line.startswith('#') line.startswith('#')
@@ -751,6 +813,12 @@ def do_debug_checks() -> bool:
return True return True
def is_not_headless() -> bool:
if headless:
log_warn(f"Cannot open dialog in headless mode")
return not headless
class PrintVersionAction(argparse.Action): class PrintVersionAction(argparse.Action):
def __init__(self, option_strings: Sequence[str], dest: str, nargs: int = 0, **kwargs: Any) -> None: def __init__(self, option_strings: Sequence[str], dest: str, nargs: int = 0, **kwargs: Any) -> None:
super().__init__(option_strings, dest, nargs, **kwargs) super().__init__(option_strings, dest, nargs, **kwargs)