Compare commits

..

1 Commits

Author SHA1 Message Date
scito
b9c552ab45 use latest cv2 also for macos 2023-02-25 10:24:56 +01:00
35 changed files with 1137 additions and 1788 deletions

3
.envrc
View File

@@ -1,3 +0,0 @@
source_url "https://raw.githubusercontent.com/cachix/devenv/95f329d49a8a5289d31e0982652f7058a189bfca/direnvrc" "sha256-d+8cBpDfDBj41inrADaJt+bDWhOktwslgoP5YiGJ1v0="
use devenv

View File

@@ -8,4 +8,4 @@ updates:
- package-ecosystem: "pip" # See documentation for possible values - package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests directory: "/" # Location of package manifests
schedule: schedule:
interval: "daily" interval: "weekly"

View File

@@ -20,20 +20,19 @@ jobs:
strategy: strategy:
matrix: matrix:
# 3.x is used to run code coverage python-version: ["3.x", "3.11", "3.10", "3.9", "3.8", "3.7"]
python-version: ["3.x", "3.13", "3.12", "3.11", "3.10", "3.9", "3.8"] platform: [ubuntu-latest, macos-latest, windows-latest]
platform: [ubuntu-latest, macos-latest, windows-latest, ubuntu-24.04-arm, macos-13]
# exclude: # exclude:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
check-latest: ${{ github.event_name == 'schedule' }} check-latest: false
- name: Install zbar shared lib for QReader (Linux) - name: Install zbar shared lib for QReader (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
@@ -53,6 +52,7 @@ jobs:
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=200 --statistics flake8 . --count --exit-zero --max-complexity=10 --max-line-length=200 --statistics
if: matrix.python-version != '3.7'
- name: Type checking with mypy - name: Type checking with mypy
run: | run: |
mypy --install-types --non-interactive src/*.py tests/*.py mypy --install-types --non-interactive src/*.py tests/*.py
@@ -60,7 +60,8 @@ jobs:
if: matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest' if: matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest'
- name: Test with pytest - name: Test with pytest
run: pytest run: pytest
if: (matrix.python-version != '3.x' || matrix.platform != 'ubuntu-latest') && (matrix.python-version != '3.10' && matrix.platform != 'macos-latest') if: (matrix.python-version != '3.x' || matrix.platform != 'ubuntu-latest')
# && matrix.platform != 'macos-latest'
- name: Test with pytest (with code coverage) - name: Test with pytest (with code coverage)
run: pytest --cov=extract_otp_secrets_test --junitxml=pytest.xml --cov-report=term-missing | tee pytest-coverage.txt run: pytest --cov=extract_otp_secrets_test --junitxml=pytest.xml --cov-report=term-missing | tee pytest-coverage.txt
if: matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest' if: matrix.python-version == '3.x' && matrix.platform == 'ubuntu-latest'

View File

@@ -25,24 +25,14 @@ on:
jobs: jobs:
build-and-push-docker-debian-image: build-and-push-docker-debian-image:
name: Build Docker Bookworm image and push to repositories name: Build Docker Bullseye image and push to repositories
# run only when code is compiling and tests are passing # run only when code is compiling and tests are passing
strategy: runs-on: ubuntu-latest
matrix:
include:
- DOCKER_ARCH: amd64
platform: ubuntu-latest
PLATFORM_ARCH: x86_64
- DOCKER_ARCH: arm64
platform: ubuntu-24.04-arm
PLATFORM_ARCH: arm64
runs-on: ${{ matrix.platform }}
# steps to perform in job # steps to perform in job
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v3
# avoid building if there are testing errors # avoid building if there are testing errors
- name: Run smoke test - name: Run smoke test
@@ -54,47 +44,50 @@ jobs:
pytest pytest
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
# setup Docker build action # setup Docker build action
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v3 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@v3 uses: docker/login-action@v2
if: github.secret_source == 'Actions' if: github.secret_source == 'Actions'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages - name: Login to Github Packages
uses: docker/login-action@v3 uses: docker/login-action@v2
if: github.secret_source == 'Actions' if: github.secret_source == 'Actions'
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GHCR_IO_TOKEN }} password: ${{ secrets.GHCR_IO_TOKEN }}
- name: "Build image (Bookworm/Debian 12) and push to Docker Hub and GitHub Container Registry" - name: "Build image and push to Docker Hub and GitHub Container Registry"
id: docker_build_qr_reader_latest id: docker_build_qr_reader_latest
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3
with: with:
platforms: linux/${{ matrix.DOCKER_ARCH }} platforms: linux/amd64,linux/arm64
# relative path to the place where source code with Dockerfile is located # relative path to the place where source code with Dockerfile is located
# TODO file:, move to docker/ # TODO file:, move to docker/
context: . context: .
file: docker/Dockerfile file: docker/Dockerfile
# builder: ${{ steps.buildx.outputs.name }} # builder: ${{ steps.buildx.outputs.name }}
# Note: tags has to be all lower-case # Note: tags has to be all lower-case
build-args: |
BASE_IMAGE=python:3.13-slim-bookworm
pull: true pull: true
tags: | tags: |
docker.io/scit0/extract_otp_secrets:latest-${{ matrix.PLATFORM_ARCH }} scit0/extract_otp_secrets:latest
docker.io/scit0/extract_otp_secrets:bookworm-${{ matrix.PLATFORM_ARCH }} scit0/extract_otp_secrets:bullseye
ghcr.io/scito/extract_otp_secrets:latest-${{ matrix.PLATFORM_ARCH }} ghcr.io/scito/extract_otp_secrets:latest
ghcr.io/scito/extract_otp_secrets:bookworm-${{ matrix.PLATFORM_ARCH }} ghcr.io/scito/extract_otp_secrets:bullseye
# build on feature branches, push only on master branch # build on feature branches, push only on master branch
push: ${{ github.ref == 'refs/heads/master' && github.secret_source == 'Actions'}} push: ${{ github.ref == 'refs/heads/master' && github.secret_source == 'Actions'}}
@@ -105,66 +98,20 @@ jobs:
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts - name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: digests_bookworm_${{ matrix.PLATFORM_ARCH }} name: debian_digests
path: digests.txt path: digests.txt
create-multiarch-debian-manifests:
name: Create multiarch manifests for Debian image
runs-on: ubuntu-latest
needs:
- build-and-push-docker-debian-image
steps:
- name: Login to DockerHub
uses: docker/login-action@v3
if: github.secret_source == 'Actions'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages
uses: docker/login-action@v3
if: github.secret_source == 'Actions'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_IO_TOKEN }}
- name: Create multiarch manifests
shell: bash
run: |
for tag in \
docker.io/scit0/extract_otp_secrets:latest \
ghcr.io/scito/extract_otp_secrets:latest \
docker.io/scit0/extract_otp_secrets:bookworm \
ghcr.io/scito/extract_otp_secrets:bookworm \
; do
docker buildx imagetools create -t $tag \
$tag-x86_64 \
$tag-arm64
done
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
# run only when code is compiling and tests are passing # run only when code is compiling and tests are passing
strategy: runs-on: ubuntu-latest
matrix:
include:
- DOCKER_ARCH: amd64
platform: ubuntu-latest
PLATFORM_ARCH: x86_64
- DOCKER_ARCH: arm64
platform: ubuntu-24.04-arm
PLATFORM_ARCH: arm64
runs-on: ${{ matrix.platform }}
# steps to perform in job # steps to perform in job
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v3
# avoid building if there are testing errors # avoid building if there are testing errors
- name: Run smoke test - name: Run smoke test
@@ -176,22 +123,22 @@ jobs:
pytest pytest
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
# setup Docker build action # setup Docker build action
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v2
if: github.secret_source == 'Actions' if: github.secret_source == 'Actions'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages - name: Login to Github Packages
uses: docker/login-action@v3 uses: docker/login-action@v2
if: github.secret_source == 'Actions' if: github.secret_source == 'Actions'
with: with:
registry: ghcr.io registry: ghcr.io
@@ -200,22 +147,22 @@ jobs:
- name: "only_txt: Build image and push to Docker Hub and GitHub Container Registry" - name: "only_txt: Build image and push to Docker Hub and GitHub Container Registry"
id: docker_build_only_txt id: docker_build_only_txt
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3
with: with:
platforms: linux/${{ matrix.DOCKER_ARCH }}
# relative path to the place where source code with Dockerfile is located # relative path to the place where source code with Dockerfile is located
platforms: linux/amd64,linux/arm64
context: . context: .
file: docker/Dockerfile_only_txt file: docker/Dockerfile_only_txt
# builder: ${{ steps.buildx.outputs.name }} # builder: ${{ steps.buildx.outputs.name }}
# Note: tags has to be all lower-case # Note: tags has to be all lower-case
pull: true pull: true
tags: | tags: |
docker.io/scit0/extract_otp_secrets:only-txt-${{ matrix.PLATFORM_ARCH }} scit0/extract_otp_secrets:only-txt
docker.io/scit0/extract_otp_secrets:alpine-${{ matrix.PLATFORM_ARCH }} scit0/extract_otp_secrets:alpine
ghcr.io/scito/extract_otp_secrets:only-txt-${{ matrix.PLATFORM_ARCH }} ghcr.io/scito/extract_otp_secrets:only-txt
ghcr.io/scito/extract_otp_secrets:alpine-${{ matrix.PLATFORM_ARCH }} ghcr.io/scito/extract_otp_secrets:alpine
# build on feature branches, push only on master branch # build on feature branches, push only on master branch
push: true # ${{ github.ref == 'refs/heads/master' && github.secret_source == 'Actions'}} push: ${{ github.ref == 'refs/heads/master' && github.secret_source == 'Actions'}}
build-args: | build-args: |
RUN_TESTS=true RUN_TESTS=true
@@ -227,68 +174,20 @@ jobs:
- name: Save docker digests as artifacts - name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: digests_alpine_${{ matrix.PLATFORM_ARCH }} name: alpine_digests
path: digests.txt path: digests.txt
create-multiarch-alpine-manifests: build-and-push-docker-buster-image:
name: Create multiarch manifests for Alpine image name: Build Docker Buster image (for PyInstsaller) and push to repositories
runs-on: ubuntu-latest
needs:
- build-and-push-docker-alpine-image
defaults:
run:
shell: bash
steps:
- name: Login to DockerHub
uses: docker/login-action@v3
if: github.secret_source == 'Actions'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages
uses: docker/login-action@v3
if: github.secret_source == 'Actions'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_IO_TOKEN }}
- name: Create multiarch manifests
shell: bash
run: |
for tag in \
docker.io/scit0/extract_otp_secrets:only-txt \
docker.io/scit0/extract_otp_secrets:alpine \
ghcr.io/scito/extract_otp_secrets:only-txt \
ghcr.io/scito/extract_otp_secrets:alpine \
; do
docker buildx imagetools create -t $tag \
$tag-x86_64 \
$tag-arm64
done
build-and-push-docker-bullseye-image:
name: Build Docker Bullseye image (for PyInstsaller) and push to repositories
# run only when code is compiling and tests are passing # run only when code is compiling and tests are passing
strategy: runs-on: ubuntu-latest
matrix:
include:
- DOCKER_ARCH: amd64
platform: ubuntu-latest
PLATFORM_ARCH: x86_64
- DOCKER_ARCH: arm64
platform: ubuntu-24.04-arm
PLATFORM_ARCH: arm64
runs-on: ${{ matrix.platform }}
# steps to perform in job # steps to perform in job
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v3
# avoid building if there are testing errors # avoid building if there are testing errors
- name: Run smoke test - name: Run smoke test
@@ -300,45 +199,50 @@ jobs:
pytest pytest
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
# setup Docker build action # setup Docker build action
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v3 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@v3 uses: docker/login-action@v2
if: github.secret_source == 'Actions' if: github.secret_source == 'Actions'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages - name: Login to Github Packages
uses: docker/login-action@v3 uses: docker/login-action@v2
if: github.secret_source == 'Actions' if: github.secret_source == 'Actions'
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GHCR_IO_TOKEN }} password: ${{ secrets.GHCR_IO_TOKEN }}
- name: "Build image from Bullseye (Debian 11) and push to GitHub Container Registry" - name: "Build image from Buster and push to GitHub Container Registry"
id: docker_build_bullseye id: docker_build_buster
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: docker/build-push-action@v5 uses: docker/build-push-action@v3
with: with:
platforms: linux/${{ matrix.DOCKER_ARCH }} platforms: linux/amd64,linux/arm64
# relative path to the place where source code with Dockerfile is located # relative path to the place where source code with Dockerfile is located
# TODO file:, move to docker/
context: . context: .
file: docker/Dockerfile file: docker/Dockerfile
# builder: ${{ steps.buildx.outputs.name }} # builder: ${{ steps.buildx.outputs.name }}
build-args: | build-args: |
BASE_IMAGE=python:3.13-slim-bullseye BASE_IMAGE=python:3.11-slim-buster
# Note: tags has to be all lower-case # Note: tags has to be all lower-case
pull: true pull: true
tags: | tags: |
docker.io/scit0/extract_otp_secrets:bullseye-${{ matrix.PLATFORM_ARCH }} scit0/extract_otp_secrets:buster
ghcr.io/scito/extract_otp_secrets:bullseye-${{ matrix.PLATFORM_ARCH }}
push: ${{ github.secret_source == 'Actions' }} push: ${{ github.secret_source == 'Actions' }}
- name: Image digest - name: Image digest
@@ -348,41 +252,7 @@ jobs:
echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt echo "${{ steps.docker_build_qr_reader_latest.outputs.digest }}" > digests.txt
- name: Save docker digests as artifacts - name: Save docker digests as artifacts
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: digests_bullseye_${{ matrix.PLATFORM_ARCH }} name: buster_digests
path: digests.txt path: digests.txt
create-multiarch-bullseye-manifests:
name: Create multiarch manifests for Bullseye image
runs-on: ubuntu-latest
needs:
- build-and-push-docker-bullseye-image
steps:
- name: Login to DockerHub
uses: docker/login-action@v3
if: github.secret_source == 'Actions'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages
uses: docker/login-action@v3
if: github.secret_source == 'Actions'
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GHCR_IO_TOKEN }}
- name: Create multiarch manifests
shell: bash
run: |
for tag in \
docker.io/scit0/extract_otp_secrets:bullseye \
ghcr.io/scito/extract_otp_secrets:bullseye \
; do
docker buildx imagetools create -t $tag \
$tag-x86_64 \
$tag-arm64
done

View File

@@ -26,8 +26,7 @@ name: release
# Build matrix: # Build matrix:
# - Linux x86_64 glibc 2.35: ubuntu-latest # - Linux x86_64 glibc 2.35: ubuntu-latest
# - Linux x86_64 glibc 2.31: extract_otp_secrets:bullseye # - Linux x86_64 glibc 2.34: extract_otp_secrets:buster
# - Linux x86_64 glibc 2.36: extract_otp_secrets:bookworm
# - Windows x86_64: windows-latest # - Windows x86_64: windows-latest
# - MacOS x86_64: macos-11 # - MacOS x86_64: macos-11
# - Linux x86_64 glibc 2.28: extract_otp_secrets:buster # - Linux x86_64 glibc 2.28: extract_otp_secrets:buster
@@ -57,7 +56,7 @@ jobs:
tag_message: ${{ steps.meta.outputs.tag_message }} tag_message: ${{ steps.meta.outputs.tag_message }}
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v3
- name: Set meta data - name: Set meta data
id: meta id: meta
# Writing to env with >> $GITHUB_ENV is an alternative # Writing to env with >> $GITHUB_ENV is an alternative
@@ -88,45 +87,42 @@ jobs:
https://api.github.com/repos/scito/extract_otp_secrets/releases \ https://api.github.com/repos/scito/extract_otp_secrets/releases \
--silent \ --silent \
--show-error \ --show-error \
-d '{"tag_name":"${{ github.ref }}","target_commitish":"master","name":"${{ steps.meta.outputs.version }} - ${{ steps.meta.outputs.date }}","body":"${{ steps.meta.outputs.tag_message }}\n\n## Executables\n\nDownload the executable for your platform and execute it, see [README.md](https://github.com/scito/extract_otp_secrets#readme)\n\n | Executable | Description |\n | --- | --- |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_x86_64 | Linux x86_64/amd64 (glibc >= 2.31) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_arm64 | Linux arm64 (glibc >= 2.31) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_x86_64.exe | Windows x86_64/amd64/x64 |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_arm64.exe | N/A |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64.dmg | N/A, see [README.md](https://github.com/scito/extract_otp_secrets#readme) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64.pkg | N/A, see [README.md](https://github.com/scito/extract_otp_secrets#readme) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64 | MacOS x86_64/amd64 (bare executable, see [README.md](https://github.com/scito/extract_otp_secrets#readme); optional libzbar must be installed manually, see [README.md](https://github.com/scito/extract_otp_secrets#readme)) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_arm64 | N/A |\n","draft":true,"prerelease":false,"generate_release_notes":true}') -d '{"tag_name":"${{ github.ref }}","target_commitish":"master","name":"${{ steps.meta.outputs.version }} - ${{ steps.meta.outputs.date }}","body":"${{ steps.meta.outputs.tag_message }}\n\n## Executables\n\nDownload the executable for your platform and execute it, see [README.md](https://github.com/scito/extract_otp_secrets#readme)\n\n | Executable | Description |\n | --- | --- |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_x86_64 | Linux x86_64/amd64 (glibc >= 2.28) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_linux_arm64 | Linux arm64 (glibc >= 2.28) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_x86_64.exe | Windows x86_64/amd64/x64 |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_win_arm64.exe | N/A |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64.dmg | N/A, see [README.md](https://github.com/scito/extract_otp_secrets#readme) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64.pkg | N/A, see [README.md](https://github.com/scito/extract_otp_secrets#readme) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_x86_64 | MacOS x86_64/amd64 (bare executable, see [README.md](https://github.com/scito/extract_otp_secrets#readme); optional libzbar must be installed manually, see [README.md](https://github.com/scito/extract_otp_secrets#readme)) |\n | extract_otp_secrets${{ steps.meta.outputs.inline_version }}_macos_arm64 | N/A |\n","draft":true,"prerelease":false,"generate_release_notes":true}')
echo upload_url=$(jq '.upload_url' <<< "$response") >> $GITHUB_OUTPUT echo upload_url=$(jq '.upload_url' <<< "$response") >> $GITHUB_OUTPUT
echo $(jq -r '.upload_url' <<< "$response") > release_url.txt echo $(jq -r '.upload_url' <<< "$response") > release_url.txt
echo $(jq -r '.id' <<< "$response") > release_id.txt echo $(jq -r '.id' <<< "$response") > release_id.txt
- name: Save Release URL File for publish - name: Save Release URL File for publish
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: release_url name: release_url
path: release_url.txt path: release_url.txt
- name: Save asset upload id for publish - name: Save asset upload id for publish
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: release_id name: release_id
path: release_id.txt path: release_id.txt
build-linux-executable-in-docker: build-linux-executable-in-docker:
name: Build ${{ matrix.platform }} release in docker container name: Build ${{ matrix.PLATFORM }} 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
needs: create-release needs: create-release
strategy: strategy:
matrix: matrix:
include: include:
- DOCKER_PLATFORM: linux/amd64 - PLATFORM: linux/amd64
platform: ubuntu-latest
EXE: extract_otp_secrets_linux_x86_64 EXE: extract_otp_secrets_linux_x86_64
ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_x86_64 ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_x86_64
- DOCKER_PLATFORM: linux/arm64 - PLATFORM: linux/arm64
platform: ubuntu-24.04-arm
EXE: extract_otp_secrets_linux_arm64 EXE: extract_otp_secrets_linux_arm64
ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_arm64 ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_arm64
runs-on: ${{ matrix.platform }}
# steps to perform in job # steps to perform in job
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v3
# avoid building if there are testing errors # avoid building if there are testing errors
- name: Run smoke test - name: Run smoke test
@@ -138,27 +134,27 @@ jobs:
pytest pytest
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v2
# setup Docker build action # setup Docker build action
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx id: buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v2
# Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381 # Workaround for failing builds: https://github.com/docker/build-push-action/issues/761#issuecomment-1383822381
# TODO remove workaround when fixed # TODO remove workaround when fixed
with: with:
driver-opts: | driver-opts: |
image=moby/buildkit:latest image=moby/buildkit:v0.10.6
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v2
if: github.secret_source == 'Actions' if: github.secret_source == 'Actions'
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Github Packages - name: Login to Github Packages
uses: docker/login-action@v3 uses: docker/login-action@v2
if: github.secret_source == 'Actions' if: github.secret_source == 'Actions'
with: with:
registry: ghcr.io registry: ghcr.io
@@ -168,21 +164,21 @@ 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_bullseye.outputs.digest }}" echo "extract_otp_secrets: ${{ steps.docker_build_buster.outputs.digest }}"
# TODO use local docker image https://stackoverflow.com/a/61155718/1663871 # TODO use local docker image https://stackoverflow.com/a/61155718/1663871
# https://github.com/multiarch/qemu-user-static # https://github.com/multiarch/qemu-user-static
# https://hub.docker.com/r/multiarch/qemu-user-static/ # https://hub.docker.com/r/multiarch/qemu-user-static/
- name: Run Pyinstaller in container for ${{ matrix.EXE }} - name: Run Pyinstaller in container for ${{ matrix.EXE }}
run: | run: |
docker run --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files docker.io/scit0/extract_otp_secrets:bullseye -c 'apt-get update && apt-get -y install binutils && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name ${{ matrix.EXE }} --distpath /files/dist/ /files/src/extract_otp_secrets.py' docker run --pull always --rm --privileged multiarch/qemu-user-static --reset -p yes
docker run --platform ${{ matrix.PLATFORM }} --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 && PYTHONHASHSEED=31 && pyinstaller -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name ${{ matrix.EXE }} --distpath /files/dist/ /files/src/extract_otp_secrets.py'
- name: Smoke tests linux/amd64 - name: Smoke tests ${{ matrix.PLATFORM }}
if: matrix.DOCKER_PLATFORM == 'linux/amd64' if: matrix.PLATFORM == 'linux/amd64'
run: | run: |
dist/${{ matrix.EXE }} -V dist/${{ matrix.EXE }} -V
dist/${{ matrix.EXE }} -h dist/${{ matrix.EXE }} -h
dist/${{ matrix.EXE }} --debug
dist/${{ matrix.EXE }} example_export.png dist/${{ matrix.EXE }} example_export.png
dist/${{ matrix.EXE }} - < example_export.txt dist/${{ matrix.EXE }} - < example_export.txt
dist/${{ matrix.EXE }} --qr ZBAR example_export.png dist/${{ matrix.EXE }} --qr ZBAR example_export.png
@@ -190,19 +186,19 @@ jobs:
dist/${{ matrix.EXE }} --qr QREADER_DEEP example_export.png dist/${{ matrix.EXE }} --qr QREADER_DEEP example_export.png
dist/${{ matrix.EXE }} --qr CV2 example_export.png dist/${{ matrix.EXE }} --qr CV2 example_export.png
dist/${{ matrix.EXE }} --qr CV2_WECHAT example_export.png dist/${{ matrix.EXE }} --qr CV2_WECHAT example_export.png
- name: Smoke tests linux/arm64 - name: Smoke tests ${{ matrix.PLATFORM }}
if: matrix.DOCKER_PLATFORM == 'linux/arm64' if: matrix.PLATFORM == 'linux/arm64'
run: | run: |
docker run --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files docker.io/scit0/extract_otp_secrets -c 'dist/${{ matrix.EXE }} -V && dist/${{ matrix.EXE }} -h && dist/${{ matrix.EXE }} example_export.png && dist/${{ matrix.EXE }} - < example_export.txt && dist/${{ matrix.EXE }} --qr ZBAR example_export.png && dist/${{ matrix.EXE }} --qr QREADER example_export.png && dist/${{ matrix.EXE }} --qr QREADER_DEEP example_export.png && dist/${{ matrix.EXE }} --qr CV2 example_export.png && dist/${{ matrix.EXE }} --qr CV2_WECHAT example_export.png' docker run --platform ${{ matrix.PLATFORM }} --pull always --entrypoint /bin/bash --rm -v "$(pwd)":/files -w /files scit0/extract_otp_secrets -c 'dist/${{ matrix.EXE }} -V && dist/${{ matrix.EXE }} -h && dist/${{ matrix.EXE }} example_export.png && dist/${{ matrix.EXE }} - < example_export.txt && dist/${{ matrix.EXE }} --qr ZBAR example_export.png && dist/${{ matrix.EXE }} --qr QREADER example_export.png && dist/${{ matrix.EXE }} --qr QREADER_DEEP example_export.png && dist/${{ matrix.EXE }} --qr CV2 example_export.png && dist/${{ matrix.EXE }} --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') if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v4 uses: actions/download-artifact@v3
with: with:
name: release_url name: release_url
- name: Display structure of files - name: Display structure of files
run: ls -R run: ls -R
- name: Upload EXE to artifacts - name: Upload EXE to artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: ${{ matrix.EXE }} name: ${{ matrix.EXE }}
path: dist/${{ matrix.EXE }} path: dist/${{ matrix.EXE }}
@@ -240,24 +236,8 @@ jobs:
ASSET_MIME: application/vnd.microsoft.portable-executable ASSET_MIME: application/vnd.microsoft.portable-executable
UPLOAD: true UPLOAD: true
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" --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\win_file_version_info.txt --name extract_otp_secrets.exe 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\win_file_version_info.txt --name extract_otp_secrets.exe src\extract_otp_secrets.py
- os: ubuntu-latest - os: macos-11
TARGET: linux
EXE: extract_otp_secrets_ubuntu
ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_x86_64_ubuntu_latest
ASSET_MIME: application/x-executable
UPLOAD: false
CMD_BUILD: |
pyinstaller -y --add-data $pythonLocation/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_ubuntu src/extract_otp_secrets.py
- os: ubuntu-24.04-arm
TARGET: linux
EXE: extract_otp_secrets_ubuntu_arm64
ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_arm64_ubuntu_latest
ASSET_MIME: application/x-executable
UPLOAD: false
CMD_BUILD: |
pyinstaller -y --add-data $pythonLocation/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_ubuntu_arm64 src/extract_otp_secrets.py
- os: macos-13
TARGET: macos TARGET: macos
# https://pyinstaller.org/en/stable/spec-files.html#spec-file-options-for-a-macos-bundle # https://pyinstaller.org/en/stable/spec-files.html#spec-file-options-for-a-macos-bundle
EXE: extract_otp_secrets EXE: extract_otp_secrets
@@ -267,23 +247,17 @@ jobs:
ASSET_MIME: application/octet-stream ASSET_MIME: application/octet-stream
UPLOAD: true UPLOAD: true
CMD_BUILD: | CMD_BUILD: |
VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2025' envsubst < installer/extract_otp_secrets_macos_template.spec > extract_otp_secrets_macos.spec VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2023' envsubst < installer/extract_otp_secrets_macos_template.spec > extract_otp_secrets_macos.spec
pyinstaller -y extract_otp_secrets_macos.spec pyinstaller -y extract_otp_secrets_macos.spec
installer/build_dmg.sh installer/build_dmg.sh
# Disable WARN: Cannot import pyzbar module. This problem is probably due to the missing zbar shared library. - os: ubuntu-latest
# - os: macos-14 TARGET: linux
# TARGET: macos EXE: extract_otp_secrets_ubuntu
# # https://pyinstaller.org/en/stable/spec-files.html#spec-file-options-for-a-macos-bundle ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_linux_x86_64_ubuntu_latest
# EXE: extract_otp_secrets ASSET_MIME: application/x-executable
# ASSET_NAME: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_macos_arm64 UPLOAD: false
# DMG: extract_otp_secrets.dmg CMD_BUILD: |
# ASSET_NAME_DMG: extract_otp_secrets${{ needs.create-release.outputs.inline_version }}_macos_arm64.dmg pyinstaller -y --add-data $pythonLocation/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_ubuntu src/extract_otp_secrets.py
# ASSET_MIME: application/octet-stream
# UPLOAD: true
# CMD_BUILD: |
# VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2025' envsubst < installer/extract_otp_secrets_macos_template.spec > extract_otp_secrets_macos.spec
# pyinstaller -y extract_otp_secrets_macos.spec
# installer/build_dmg.sh
steps: steps:
- name: Output path - name: Output path
if: runner.os == 'Windows' if: runner.os == 'Windows'
@@ -291,14 +265,14 @@ jobs:
- name: List Windir - name: List Windir
if: runner.os == 'Windows' if: runner.os == 'Windows'
run: ls "$($Env:WinDir)\system32" run: ls "$($Env:WinDir)\system32"
- uses: actions/checkout@v4 - 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
run: echo "macos_python_path=/Library/Frameworks/Python.framework/Versions/3.13" >> $GITHUB_ENV run: echo "macos_python_path=/Library/Frameworks/Python.framework/Versions/3.11" >> $GITHUB_ENV
- name: Set up Python 3.13 - name: Set up Python 3.11
uses: actions/setup-python@v5 uses: actions/setup-python@v4
with: with:
python-version: 3.13 python-version: 3.11
check-latest: true check-latest: true
- name: Install zbar shared lib for QReader (Linux) - name: Install zbar shared lib for QReader (Linux)
if: runner.os == 'Linux' if: runner.os == 'Linux'
@@ -318,7 +292,7 @@ jobs:
shell: bash shell: bash
run: | run: |
mkdir -p build/ mkdir -p build/
VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n "s/^([0-9]+).*/\1/p") VERSION_BUILD=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n -e"s/^[0-9]+.+/99/p")$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) COPYRIGHT_YEARS='2020-2025' envsubst < installer/win_file_version_info_template.txt > build/win_file_version_info.txt VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n "s/^([0-9]+).*/\1/p") VERSION_BUILD=$(echo $(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") | sed -E -n -e"s/^[0-9]+.+/99/p")$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) COPYRIGHT_YEARS='2020-2023' envsubst < installer/win_file_version_info_template.txt > build/win_file_version_info.txt
- name: Build with pyinstaller for ${{ matrix.TARGET }} - name: Build with pyinstaller for ${{ matrix.TARGET }}
env: env:
# Reproducible build: https://pyinstaller.org/en/stable/advanced-topics.html#creating-a-reproducible-build # Reproducible build: https://pyinstaller.org/en/stable/advanced-topics.html#creating-a-reproducible-build
@@ -328,7 +302,6 @@ jobs:
run: | run: |
dist/${{ matrix.EXE }} -V dist/${{ matrix.EXE }} -V
dist/${{ matrix.EXE }} -h dist/${{ matrix.EXE }} -h
dist/${{ matrix.EXE }} --debug
dist/${{ matrix.EXE }} example_export.png dist/${{ matrix.EXE }} example_export.png
dist/${{ matrix.EXE }} --qr ZBAR example_export.png dist/${{ matrix.EXE }} --qr ZBAR example_export.png
dist/${{ matrix.EXE }} --qr QREADER example_export.png dist/${{ matrix.EXE }} --qr QREADER example_export.png
@@ -341,12 +314,12 @@ jobs:
dist/${{ matrix.EXE }} - < example_export.txt dist/${{ matrix.EXE }} - < 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') if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v4 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') if: startsWith(github.ref, 'refs/tags/v')
uses: actions/download-artifact@v4 uses: actions/download-artifact@v3
with: with:
name: release_id name: release_id
- name: Display structure of files - name: Display structure of files
@@ -359,12 +332,12 @@ jobs:
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 EXE to artifacts - name: Upload EXE to artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: ${{ matrix.EXE }} name: ${{ matrix.EXE }}
path: dist/${{ matrix.EXE }} path: dist/${{ matrix.EXE }}
- name: Upload DMG to artifacts - name: Upload DMG to artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
if: runner.os == 'macOS' if: runner.os == 'macOS'
with: with:
name: ${{ matrix.DMG }} name: ${{ matrix.DMG }}
@@ -389,7 +362,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Load Release Id File from release job - name: Load Release Id File from release job
uses: actions/download-artifact@v4 uses: actions/download-artifact@v3
with: with:
name: release_id name: release_id
- name: Set meta data - name: Set meta data

View File

@@ -42,26 +42,26 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v3 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file. # By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file. # Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality # queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@v3 uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines. # If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: | # - run: |
@@ -69,6 +69,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh # ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3 uses: github/codeql-action/analyze@v2
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

14
.gitignore vendored
View File

@@ -27,20 +27,6 @@ file_version_info.txt
assets/* assets/*
extract_otp_secrets_linux_arm64.spec extract_otp_secrets_linux_arm64.spec
extract_otp_secrets_linux_x86_64_bullseye.spec extract_otp_secrets_linux_x86_64_bullseye.spec
extract_otp_secrets_linux_x86_64_bookworm.spec
extract_otp_secrets_linux_x86_64.spec extract_otp_secrets_linux_x86_64.spec
extract_otp_secrets.spec extract_otp_secrets.spec
test.txt test.txt
extract_otp_secrets.build/
extract_otp_secrets.dist/
extract_otp_secrets.onefile-build/
extract_otp_secrets.bin
# Devenv
.devenv*
devenv.local.nix
# direnv
.direnv
# pre-commit
.pre-commit-config.yaml

View File

@@ -2,7 +2,7 @@
"python.testing.pytestArgs": [ "python.testing.pytestArgs": [
"." "."
], ],
"python.testing.unittestEnabled": false, "python.testing.unittestEnabled": true,
"python.testing.pytestEnabled": true, "python.testing.pytestEnabled": true,
"cSpell.words": [ "cSpell.words": [
"devbox", "devbox",

View File

@@ -7,6 +7,7 @@ name = "pypi"
colorama = ">=0.4.6" colorama = ">=0.4.6"
opencv-contrib-python = "*" 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 = "*"
pillow = "*" pillow = "*"
pyzbar = "*" pyzbar = "*"
protobuf = "*" protobuf = "*"
@@ -19,7 +20,6 @@ flake8 = "*"
gfm-toc = "*" gfm-toc = "*"
mypy = "*" mypy = "*"
mypy-protobuf = "*" mypy-protobuf = "*"
nuitka = "*"
pylint = "*" pylint = "*"
pytest = "*" pytest = "*"
pytest-cov = "*" pytest-cov = "*"

923
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,10 +2,10 @@
[![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-95%25-brightgreen) ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
[![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)
![python versions](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-blue) ![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) [![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)
@@ -14,7 +14,7 @@
[![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) <!-- ![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) [![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-5.29.3-informational)--> ![protobuf version](https://img.shields.io/badge/protobuf-4.22.0-informational)-->
<!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) --> <!-- [![Github all releases](https://img.shields.io/github/downloads/scito/extract_otp_secrets/total.svg)](https://GitHub.com/scito/extract_otp_secrets/releases/) -->
@@ -36,7 +36,6 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
## Table of contents ## Table of contents
- [Table of contents](#table-of-contents)
- [Download and run binary executable (🆕 since v2.1)](#download-and-run-binary-executable--since-v21) - [Download and run binary executable (🆕 since v2.1)](#download-and-run-binary-executable--since-v21)
- [MacOS](#macos) - [MacOS](#macos)
- [Usage](#usage) - [Usage](#usage)
@@ -47,7 +46,7 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
- [Installation of optional shared system libraries (recommended)](#installation-of-optional-shared-system-libraries-recommended) - [Installation of optional shared system libraries (recommended)](#installation-of-optional-shared-system-libraries-recommended)
- [Program help: arguments and options](#program-help-arguments-and-options) - [Program help: arguments and options](#program-help-arguments-and-options)
- [Examples](#examples) - [Examples](#examples)
- [Printing otp secrets from text file](#printing-otp-secrets-from-text-file) - [Printing otp secrets from text file](#printing-otp-secrets-form-text-file)
- [Printing otp secrets from image file](#printing-otp-secrets-from-image-file) - [Printing otp secrets from image file](#printing-otp-secrets-from-image-file)
- [Writing otp secrets to csv file](#writing-otp-secrets-to-csv-file) - [Writing otp secrets to csv file](#writing-otp-secrets-to-csv-file)
- [Writing otp secrets to json file](#writing-otp-secrets-to-json-file) - [Writing otp secrets to json file](#writing-otp-secrets-to-json-file)
@@ -67,8 +66,7 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
- [pipenv](#pipenv) - [pipenv](#pipenv)
- [Visual Studio Code Remote - Containers / VSCode devcontainer](#visual-studio-code-remote---containers--vscode-devcontainer) - [Visual Studio Code Remote - Containers / VSCode devcontainer](#visual-studio-code-remote---containers--vscode-devcontainer)
- [venv](#venv) - [venv](#venv)
- [devbox 1](#devbox-1) - [devbox](#devbox)
- [devbox 2](#devbox-2)
- [docker](#docker) - [docker](#docker)
- [More docker examples](#more-docker-examples) - [More docker examples](#more-docker-examples)
- [Tests](#tests) - [Tests](#tests)
@@ -87,7 +85,6 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
- [Problems and Troubleshooting](#problems-and-troubleshooting) - [Problems and Troubleshooting](#problems-and-troubleshooting)
- [Windows error message](#windows-error-message) - [Windows error message](#windows-error-message)
- [Related projects](#related-projects) - [Related projects](#related-projects)
- [Third party documentation](#third-party-documentation)
</details> </details>
## Download and run binary executable (🆕 since v2.1) ## Download and run binary executable (🆕 since v2.1)
@@ -120,25 +117,19 @@ The secrets can be exported to JSON or CSV, or printed as QR codes to console or
However, the bare executable can be executed from the command line: However, the bare executable can be executed from the command line:
1. Download executable for macOS platform from [latest release](https://github.com/scito/extract_otp_secrets/releases/latest), see assets 1. Download executable for macOS platform from [latest release](https://github.com/scito/extract_otp_secrets/releases/latest), see assets
2. Open `Terminal` application 2. Open Terminal application
3. Change to Downloads folder in Terminal: `cd $HOME/Downloads` 3. Change to Downloads: `cd $HOME/Downloads`
4. Remove quarantine bit for the downloaded file: `xattr -r -d com.apple.quarantine extract_otp_secrets_X.Y.Z_macos_x86_64` 4. Set executable bit for the downloaded file: `chmod +x extract_otp_secrets_X.Y.Z_macos_x86_64`
5. Set executable bit for the downloaded file: `chmod +x extract_otp_secrets_X.Y.Z_macos_x86_64` 5. Start executable from command line: `./extract_otp_secrets_X.Y.Z_macos_x86_64`
6. Start executable from command line for the first time: `./extract_otp_secrets_X.Y.Z_macos_x86_64`
7. Wait approximately 30 seconds to 1 minute on the first run. Terminal will display the following error:
```
OpenCV: not authorized to capture video (status 0), requesting...
OpenCV: camera failed to properly initialize!
```
8. macOS will then prompt to request camera access.
9. After allowing camera access, rerun the program.
10. On the second run, the GUI prompt shows correctly and is fully operable: `./extract_otp_secrets_X.Y.Z_macos_x86_64`
:information_source: Replace `X.Y.Z` in above commands with the version number of your downloaded file, e.g. `extract_otp_secrets_2.4.0_macos_x86_64` #### Apple Silicon (ARM)
:information_source: If Rosetta2 emulation is installed, these steps work also for M1 and M2 Apple Silicon processors and the program can be executed directly. Currently, there is no build for M1 and M2 Apple Silicon processors due to lack of hardware. However, the binary executable should be runnable by Rosetta2 emulation.
Tested with extract_otp_secrets_2.8.1_macos_x86_64 on macOS Sequoia 15.1 beta. Source: [#283](https://github.com/scito/extract_otp_secrets/issues/283) ```
arch -x86_64 extract_otp_secrets_X.Y.Z_macos_x86_64
```
:warning: This command is untested due to lack of hardware.
## Usage ## Usage
@@ -246,7 +237,7 @@ OpenCV requires [Visual C++ redistributable 2015](https://www.microsoft.com/en-u
## Program help: arguments and options ## Program help: arguments and options
<pre>usage: extract_otp_secrets.py [-h] [--csv FILE] [--keepass FILE] [--json FILE] [--txt FILE] [--urls FILE] [--printqr] [--saveqr DIR] [--camera NUMBER] [--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}] [-i] [--no-color] [--version] [-d | -v | -q] [infile ...] <pre>usage: extract_otp_secrets.py [-h] [--csv FILE] [--keepass FILE] [--json FILE] [--txt FILE] [--printqr] [--saveqr DIR] [--camera NUMBER] [--qr {ZBAR,QREADER,QREADER_DEEP,CV2,CV2_WECHAT}] [-i] [--no-color] [--version] [-d | -v | -q] [infile ...]
Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps
If no infiles are provided, a GUI window starts and QR codes are captured from the camera. If no infiles are provided, a GUI window starts and QR codes are captured from the camera.
@@ -257,11 +248,10 @@ positional arguments:
options: options:
-h, --help show this help message and exit -h, --help show this help message and exit
--csv FILE, -c FILE export csv file, or - for stdout --csv FILE, -c FILE export csv file or - for stdout
--keepass FILE, -k FILE export totp/hotp csv file(s) for KeePass, - for stdout --keepass FILE, -k FILE export totp/hotp csv file(s) for KeePass, - for stdout
--json FILE, -j FILE export json file or - for stdout --json FILE, -j FILE export json file or - for stdout
--txt FILE, -t FILE export txt file or - for stdout --txt FILE, -t FILE export txt file or - for stdout
--urls FILE, -u FILE export file with list of otpauth urls, or - for stdout
--printqr, -p print QR code(s) as text to the terminal --printqr, -p print QR code(s) as text to the terminal
--saveqr DIR, -s DIR save QR code(s) as images to directory --saveqr DIR, -s DIR save QR code(s) as images to directory
--camera NUMBER, -C NUMBER camera number of system (default camera: 0) --camera NUMBER, -C NUMBER camera number of system (default camera: 0)
@@ -283,7 +273,7 @@ python extract_otp_secrets.py = < example_export.png</pre>
## Examples ## Examples
### Printing otp secrets from text file ### Printing otp secrets form text file
python src/extract_otp_secrets.py example_export.txt python src/extract_otp_secrets.py example_export.txt
@@ -360,8 +350,8 @@ python extract_otp_secrets.py = < example_export.png</pre>
* Prints errors and warnings to stderr (🆕 since v2.0) * Prints errors and warnings to stderr (🆕 since v2.0)
* Prints colored output (🆕 since v2.0) * Prints colored output (🆕 since v2.0)
* Startable as executable (script, Python, and all dependencies packed in one executable) (🆕 since v2.1) * Startable as executable (script, Python, and all dependencies packed in one executable) (🆕 since v2.1)
* extract_otp_secrets_linux_x86_64 (requires glibc >= 2.31) * extract_otp_secrets_linux_x86_64 (requires glibc >= 2.28)
* extract_otp_secrets_linux_arm64 (requires glibc >= 2.31) * extract_otp_secrets_linux_arm64 (requires glibc >= 2.28)
* extract_otp_secrets_win_x86_64.exe * extract_otp_secrets_win_x86_64.exe
* extract_otp_secrets_macos_x86_64 (optional [libzbar](#installation-of-optional-shared-system-libraries-recommended) needs to be installed manually if needed) * extract_otp_secrets_macos_x86_64 (optional [libzbar](#installation-of-optional-shared-system-libraries-recommended) needs to be installed manually if needed)
* extract_otp_secrets_macos_x86_64.dmg N/A, see [why](#macos) * extract_otp_secrets_macos_x86_64.dmg N/A, see [why](#macos)
@@ -380,7 +370,7 @@ python extract_otp_secrets.py = < example_export.png</pre>
* macOS * macOS
* Windows * Windows
* Uses UTF-8 on all platforms * Uses UTF-8 on all platforms
* Supports Python >= 3.8 * Supports Python >= 3.7
* Installation of shared system libraries is optional (🆕 since v2.3) * Installation of shared system libraries is optional (🆕 since v2.3)
* Provides a debug mode (-d) for analyzing import problems * Provides a debug mode (-d) for analyzing import problems
* Written in modern Python using type hints and following best practices * Written in modern Python using type hints and following best practices
@@ -520,7 +510,7 @@ Alternatively, you can use a python virtual env for the dependencies:
The requirements\*.txt files contain all the dependencies (also the optional ones). The requirements\*.txt files contain all the dependencies (also the optional ones).
To leave the python virtual env just call `deactivate`. To leave the python virtual env just call `deactivate`.
### devbox 1 ### 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: 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:
@@ -528,14 +518,6 @@ Install [devbox](https://github.com/jetpack-io/devbox), which is a wrapper for n
devbox shell devbox shell
``` ```
### devbox 2
Install [devbox](https://devenv.sh), which is a wrapper for nix. Then enter the environment with Python and the packages installed with:
```
devenv shell
```
### docker ### docker
Install [Docker](https://docs.docker.com/get-docker/). Install [Docker](https://docs.docker.com/get-docker/).
@@ -545,35 +527,35 @@ Prebuilt docker images are available for amd64 and arm64 architectures on [Docke
Extracting from an QR image file: Extracting from an QR image file:
``` ```
curl -s https://raw.githubusercontent.com/scito/extract_otp_secrets/master/example_export.png | docker run --network none --pull always -i --rm -v "$(pwd)":/files:ro docker.io/scit0/extract_otp_secrets = curl -s https://raw.githubusercontent.com/scito/extract_otp_secrets/master/example_export.png | docker run --pull always -i --rm -v "$(pwd)":/files:ro scit0/extract_otp_secrets =
``` ```
Capturing from camera in GUI window (X Window system required on host): Capturing from camera in GUI window (X Window system required on host):
``` ```
docker run --network none --pull always --rm -v "$(pwd)":/files:ro -i --device="/dev/video0:/dev/video0" --env="DISPLAY" -v /tmp/.X11-unix:/tmp/.X11-unix:ro docker.io/scit0/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
``` ```
If only text processing is required, there is a small Image based on Alpine Linux: If only text processing is required, there is a small Image based on Alpine Linux:
``` ```
curl -s https://raw.githubusercontent.com/scito/extract_otp_secrets/master/example_export.txt | docker run --network none --pull always -i --rm -v "$(pwd)":/files:ro docker.io/scit0/extract_otp_secrets:latest-only-txt - curl -s https://raw.githubusercontent.com/scito/extract_otp_secrets/master/example_export.txt | docker run --pull always -i --rm -v "$(pwd)":/files:ro scit0/extract_otp_secrets:latest-only-txt -
``` ```
Docker image from GitHub: Docker image from GitHub:
``` ```
docker login ghcr.io -u USERNAME docker login ghcr.io -u USERNAME
curl -s https://raw.githubusercontent.com/scito/extract_otp_secrets/master/example_export.png | docker run --network none --pull always -i --rm -v "$(pwd)":/files:ro ghcr.io/scito/extract_otp_secrets = curl -s https://raw.githubusercontent.com/scito/extract_otp_secrets/master/example_export.png | docker run --pull always -i --rm -v "$(pwd)":/files:ro ghcr.io/scito/extract_otp_secrets =
``` ```
### More docker examples ### More docker examples
docker run --network none --pull always --rm -v "$(pwd)":/files:ro docker.io/scit0/extract_otp_secrets example_export.png docker run --pull always --rm -v "$(pwd)":/files:ro scit0/extract_otp_secrets example_export.png
docker run --network none --pull always --rm -i -v "$(pwd)":/files:ro docker.io/scit0/extract_otp_secrets:latest-only-txt - < example_export.txt docker run --pull always --rm -i -v "$(pwd)":/files:ro scit0/extract_otp_secrets_only_txt - < example_export.txt
cat example_export.txt | docker run --network none --pull always --rm -i -v "$(pwd)":/files:ro docker.io/scit0/extract_otp_secrets:latest-only-txt - -c - > example_out.csv cat example_export.txt | docker run --pull always --rm -i -v "$(pwd)":/files:ro scit0/extract_otp_secrets:latest_only_txt - -c - > example_out.csv
## Tests ## Tests
@@ -706,15 +688,10 @@ Build extract_otp_secrets project
Options: Options:
-i Interactive mode, all steps must be confirmed -i Interactive mode, all steps must be confirmed
-C Ignore version check of protobuf/protoc -C Ignore version check of protobuf/protoc
-e Build exe -E Do not build exe
-n Build nuitka exe -D Do not build docker
-L Do not build local (exes)
-d Build docker
-a Build arm
-X Do not build x86_64
-B Do not build base
-V Do not run pipenv -V Do not run pipenv
-g Start extract_otp_secrets.py in GUI mode -G Do not start extract_otp_secrets.py in GUI mode
-c Clean everything -c Clean everything
-r Generate result files -r Generate result files
-h, --help Show help and quit -h, --help Show help and quit
@@ -729,7 +706,7 @@ Command for regeneration of Python code from proto3 message definition file (onl
protoc --plugin=protoc-gen-mypy=path/to/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python src/google_auth.proto protoc --plugin=protoc-gen-mypy=path/to/protoc-gen-mypy --python_out=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python src/google_auth.proto
The generated protobuf Python code was generated by protoc 29.3 (https://github.com/protocolbuffers/protobuf/releases/tag/v29.3). The generated protobuf Python code was generated by protoc 22.0 (https://github.com/protocolbuffers/protobuf/releases/tag/v22.0).
For Python type hint generation the [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) package is used. For Python type hint generation the [mypy-protobuf](https://github.com/nipunn1313/mypy-protobuf) package is used.
@@ -740,7 +717,7 @@ For Python type hint generation the [mypy-protobuf](https://github.com/nipunn131
## Issues ## Issues
* Segmentation fault on macOS with CV2 4.7.0: https://github.com/opencv/opencv/issues/23072 (fixed) * Segmentation fault on macOS with CV2 4.7.0: https://github.com/opencv/opencv/issues/23072
* CV2 window does not show icons: https://github.com/opencv/opencv-python/issues/585 * CV2 window does not show icons: https://github.com/opencv/opencv-python/issues/585
## Problems and Troubleshooting ## Problems and Troubleshooting
@@ -773,18 +750,13 @@ FileNotFoundError: Could not find module 'libiconv.dll' (or one of its dependenc
## Related projects ## Related projects
* [ZBar](https://github.com/mchehab/zbar) is an open source software suite for reading bar codes from various sources, including webcams. * [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. This app can scan Google export QR codes and export the secrets, e.g. as JSON. However, a second device is required. * [Aegis Authenticator](https://github.com/beemdevelopment/Aegis) is a free, secure and open source 2FA app for Android.
* [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar) is a good QR code reader Python module * [pyzbar](https://github.com/NaturalHistoryMuseum/pyzbar) is a good QR code reader Python module
* [OpenCV](https://docs.opencv.org/4.x/) (CV2) Open Source Computer Vision library with [opencv-python](https://github.com/opencv/opencv-python) * [OpenCV](https://docs.opencv.org/4.x/) (CV2) Open Source Computer Vision library with [opencv-python](https://github.com/opencv/opencv-python)
* [Python QReader](https://github.com/Eric-Canas/QReader) Python QR code readers * [Python QReader](https://github.com/Eric-Canas/QReader) Python QR code readers
* [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.] * [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.]
* [Google Authenticator secret extractor](https://github.com/krissrex/google-authenticator-exporter) is similar project written in JavaScript. It also extracts otp secrets from Google Authenticator. * [Google Authenticator secret extractor](https://github.com/krissrex/google-authenticator-exporter) is similar project written in JavaScript. It also extracts otp secrets from Google Authenticator.
## Third party documentation
* [TOTP Secret Extraction from QR codes (medium.com)](https://cavalloj.medium.com/totp-secret-extraction-from-qr-codes-ee097b4c687f)
* [Google Authenticator: OTP-Secrets auslesen (stadt-bremerhaven.de)](https://stadt-bremerhaven.de/google-authenticator-otp-secrets-auslesen/)
*** ***
# #StandWithUkraine 🇺🇦 # #StandWithUkraine 🇺🇦

723
build.sh
View File

@@ -77,15 +77,10 @@ interactive=false
ignore_version_check=true ignore_version_check=true
clean=false clean=false
clean_flag="" clean_flag=""
build_base=true build_docker=true
build_arm=false build_exe=true
build_x86_64=true
build_docker=false
build_local=true
build_exe=false
build_nuitka_exe=false
run_pipenv=true run_pipenv=true
run_gui=false run_gui=true
generate_result_files=false generate_result_files=false
PYTHONHASHSEED=31 PYTHONHASHSEED=31
@@ -99,21 +94,16 @@ while test $# -gt 0; do
echo "Options:" echo "Options:"
echo "-i Interactive mode, all steps must be confirmed" echo "-i Interactive mode, all steps must be confirmed"
echo "-C Ignore version check of protobuf/protoc" echo "-C Ignore version check of protobuf/protoc"
echo "-e Build exe" echo "-E Do not build exe"
echo "-n Build nuitka exe" echo "-D Do not build docker"
echo "-L Do not run protoc and base build locally incl. exes"
echo "-d Build docker"
echo "-a Build arm"
echo "-X Do not build x86_64"
echo "-B Do not build base"
echo "-V Do not run pipenv" echo "-V Do not run pipenv"
echo "-g Start extract_otp_secrets.py in GUI mode" echo "-G Do not start extract_otp_secrets.py in GUI mode"
echo "-c Clean everything" echo "-c Clean everything"
echo "-r Generate result files" echo "-r Generate result files"
echo "-h, --help Show help and quit" echo "-h, --help Show help and quit"
quit quit
;; ;;
-i) -a)
interactive=true interactive=true
shift shift
;; ;;
@@ -121,41 +111,20 @@ while test $# -gt 0; do
ignore_version_check=false ignore_version_check=false
shift shift
;; ;;
-B) -E)
build_base=false build_exe=false
shift shift
;; ;;
-L) -D)
build_local=false build_docker=false
build_base=false
shift
;;
-a)
build_arm=true
shift
;;
-X)
build_x86_64=false
shift
;;
-e)
build_exe=true
shift
;;
-n)
build_nuitka_exe=true
shift
;;
-d)
build_docker=true
shift shift
;; ;;
-V) -V)
run_pipenv=false run_pipenv=false
shift shift
;; ;;
-g) -G)
run_gui=true run_gui=false
shift shift
;; ;;
-r) -r)
@@ -217,436 +186,326 @@ if $clean; then
cmd="sudo pipenv --rm || true" cmd="sudo pipenv --rm || true"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
fi
cmd="mkdir -p dist" cmd="$PIP install --use-pep517 -U -r requirements-dev.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
echo -e "\n\nChecking 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
OLDVERSION=$(cat $BIN/$DEST/.VERSION.txt || echo "")
echo -e "\nProtoc remote version $VERSION\n"
echo -e "Protoc local version: $OLDVERSION\n"
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=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python --proto_path=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="pip install -U 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"
# Lint
LINT_OUT_FILE="tests/reports/flake8_results.txt"
cmd="$FLAKE8 . --count --select=E9,F63,F7,F82 --show-source --statistics | tee $LINT_OUT_FILE"
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 | tee -a $LINT_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Type checking
TYPE_CHECK_OUT_FILE="tests/reports/mypy_results.txt"
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 | tee $TYPE_CHECK_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Generate results files
if $generate_result_files; then
cmd="for color in '' '-n'; do for level in '' '-v' '-vv' '-vvv'; do $PYTHON src/extract_otp_secrets.py example_export.txt \$color \$level > tests/data/print_verbose_output\$color\$level.txt; done; done"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
fi fi
if $build_local; then # pip -e install
cmd="$PIP install --use-pep517 -U -r requirements-dev.txt"
cmd="$PIP install -U -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 (needs module)
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_FILE="tests/reports/pytest-coverage.txt"
cmd="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_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Pipenv
if $run_pipenv; then
cmd="$PIP install -U pipenv"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
echo -e "\n\nChecking Protoc version..." $PIPENV --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
OLDVERSION=$(cat $BIN/$DEST/.VERSION.txt || echo "") cmd="$PIPENV update && $PIPENV --rm && $PIPENV install"
echo -e "\nProtoc remote version $VERSION\n"
echo -e "Protoc local version: $OLDVERSION\n"
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=src/protobuf_generated_python --mypy_out=src/protobuf_generated_python --proto_path=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="pip install -U pip"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
$PIP --version $PIPENV run python --version
cmd="$PIP install --use-pep517 -U -r requirements.txt" cmd="$PIPENV run pytest --cov=extract_otp_secrets_test tests/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
if $build_base; then
# Lint
LINT_OUT_FILE="tests/reports/flake8_results.txt"
cmd="$FLAKE8 . --count --select=E9,F63,F7,F82 --show-source --statistics | tee $LINT_OUT_FILE"
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 | tee -a $LINT_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Type checking
TYPE_CHECK_OUT_FILE="tests/reports/mypy_results.txt"
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 | tee $TYPE_CHECK_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Generate results files
if $generate_result_files; then
cmd="for color in '' '-n'; do for level in '' '-v' '-vv' '-vvv'; do $PYTHON src/extract_otp_secrets.py example_export.txt \$color \$level > tests/data/print_verbose_output\$color\$level.txt; done; done"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# pip -e install
cmd="$PIP install -U -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 (needs module)
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_FILE="tests/reports/pytest-coverage.txt"
cmd="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_FILE"
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_FILE | 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
# Pipenv
if $run_pipenv; then
cmd="$PIP install -U pipenv"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
$PIPENV --version
cmd="$PIPENV --rm && $PIPENV update && $PIPENV install"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
$PIPENV run python --version
cmd="$PIPENV run pytest --cov=extract_otp_secrets_test tests/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Build wheel
cmd="$PIP wheel ."
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Build executable
if $build_exe; then
cmd="LOCAL_GLIBC_VERSION=$(ldd --version | sed '1!d' | sed -E 's/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/')"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
echo "local glibc: $LOCAL_GLIBC_VERSION"
cmd="time pyinstaller -y --specpath installer --add-data $HOME/.local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile $clean_flag src/extract_otp_secrets.py"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets --qr ZBAR example_export.png"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Build compiled executable
if $build_nuitka_exe; then
cmd="LOCAL_GLIBC_VERSION=$(ldd --version | sed '1!d' | sed -E 's/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/')"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
echo "local glibc: $LOCAL_GLIBC_VERSION"
cmd="$PIP install -U pyqt5"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="time $PYTHON -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --noinclude-default-mode=nofollow --clang --include-data-dir=$HOME/.local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=build/nuitka --output-filename=extract_otp_secrets_compiled src/extract_otp_secrets.py"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="cp build/nuitka/extract_otp_secrets_compiled dist/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets_compiled -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets_compiled --qr ZBAR example_export.png"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Generate README.md TOC
cmd="gfm-toc -s 2 -e 3 -t -o README.md > docs/README_TOC.md"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# create Windows win_file_version_info.txt
cmd="VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") VERSION_BUILD=$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) COPYRIGHT_YEARS='2020-2023' envsubst < installer/win_file_version_info_template.txt > build/win_file_version_info.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# create macOS extract_otp_secrets_macos.spec from extract_otp_secrets_macos_template.spec
cmd="VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2023' envsubst < installer/extract_otp_secrets_macos_template.spec > build/extract_otp_secrets_macos.spec"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
fi fi
# Build wheel
cmd="$PIP wheel ."
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Build executable
if $build_exe; then
cmd="LOCAL_GLIBC_VERSION=$(ldd --version | sed '1!d' | sed -E 's/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/')"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
echo "local glibc: $LOCAL_GLIBC_VERSION"
cmd="pyinstaller -y --specpath installer --add-data $HOME/.local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile $clean_flag src/extract_otp_secrets.py"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
# Generate README.md TOC
cmd="gfm-toc -s 2 -e 3 -t -o README.md > docs/README_TOC.md"
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_FILE | 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 if $build_docker; then
# Build docker # Build docker
if $build_x86_64; then # Build Dockerfile_only_txt (Alpine)
# Build Dockerfile_only_txt (Alpine) cmd="docker build . -t extract_otp_secrets_only_txt -t extract_otp_secrets:only-txt -t extract_otp_secrets:alpine -f docker/Dockerfile_only_txt --pull --build-arg RUN_TESTS=false"
cmd="docker build . -t extract_otp_secrets_only_txt -t extract_otp_secrets:only-txt -t extract_otp_secrets:alpine -f docker/Dockerfile_only_txt --pull --build-arg RUN_TESTS=false" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd"
eval "$cmd"
cmd="docker run --network none --rm -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt example_export.txt" 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 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --network none --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets_only_txt - < example_export.txt" 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 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="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" cmd="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"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
# Build extract_otp_secrets (Debian Bookworm) # Build extract_otp_secrets (Debian Bullseye)
cmd="docker build . -t extract_otp_secrets -t extract_otp_secrets:bookworm --pull -f docker/Dockerfile --build-arg RUN_TESTS=false" cmd="docker build . -t extract_otp_secrets -t extract_otp_secrets:bullseye --pull -f docker/Dockerfile --build-arg RUN_TESTS=false"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --network none --rm -v \"$(pwd)\":/files:ro extract_otp_secrets example_export.txt" 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 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="cat example_export.txt | docker run --network none --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets - -c - > example_output.csv" cmd="cat example_export.txt | docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets - -c - > example_output.csv"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --network none --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets = < example_export.png" cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets = < example_export.png"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets" 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 if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
# Build extract_otp_secrets (Debian Bullseye) # Build extract_otp_secrets (Debian Buster)
cmd="docker build . -t extract_otp_secrets:bullseye -t extract_otp_secrets:bullseye-x86_64 --pull -f docker/Dockerfile --build-arg RUN_TESTS=false --build-arg BASE_IMAGE=python:3.12-slim-bullseye" cmd="docker build . -t extract_otp_secrets:buster --pull -f docker/Dockerfile --build-arg RUN_TESTS=false --build-arg BASE_IMAGE=python:3.11-slim-buster"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --network none --rm -v \"$(pwd)\":/files:ro extract_otp_secrets:bullseye example_export.txt" cmd="docker run --rm -v \"$(pwd)\":/files:ro extract_otp_secrets:buster example_export.txt"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="cat example_export.txt | docker run --network none --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets:bullseye - -c - > example_output.csv" cmd="cat example_export.txt | docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets:buster - -c - > example_output.csv"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --network none --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets:bullseye = < example_export.png" cmd="docker run --rm -i -v \"$(pwd)\":/files:ro extract_otp_secrets:buster = < example_export.png"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets:bullseye" cmd="docker run --entrypoint /extract/run_pytest.sh --rm -v \"$(pwd)\":/files:ro extract_otp_secrets:buster"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
# Build executable from Docker latest # Build executable from Docker latest
# sed "1!d" is workaround for head -n 1 since it head procduces exit code != 0 # sed "1!d" is workaround for head -n 1 since it head procduces exit code != 0
BOOKWORM_GLIBC_VERSION=$(docker run --network none --entrypoint /bin/bash --rm extract_otp_secrets -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"') BULLSEYE_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
echo "Bookworm glibc: $BOOKWORM_GLIBC_VERSION" echo "Bullseye glibc: $BULLSEYE_GLIBC_VERSION"
fi
if $build_arm; then
# build linux/arm64
cmd="docker run --network none --pull always --rm --privileged multiarch/qemu-user-static --reset -p yes"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Build extract_otp_secrets (Debian Bullseye)
cmd="docker buildx build --platform=linux/arm64 . -t extract_otp_secrets:bullseye-arm64 --pull -f docker/Dockerfile --build-arg RUN_TESTS=false --build-arg BASE_IMAGE=python:3.12-slim-bullseye"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
if $build_exe; then if $build_exe; then
if $build_x86_64; then cmd="docker run --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64_bullseye --distpath /files/dist/ /files/src/extract_otp_secrets.py'"
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_x86_64_bookworm --distpath /files/dist/ /files/src/extract_otp_secrets.py'" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi eval "$cmd"
eval "$cmd"
cmd="dist/extract_otp_secrets_linux_x86_64_bookworm -h || echo 'Could not run exe; see error message above'" cmd="dist/extract_otp_secrets_linux_x86_64_bullseye -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="dist/extract_otp_secrets_linux_x86_64_bookworm --qr ZBAR example_export.png || echo 'Could not run exe; see error message above'" # Build executable from Docker buster
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi BUSTER_GLIBC_VERSION=$(docker run --entrypoint /bin/bash --rm extract_otp_secrets:buster -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
eval "$cmd" echo "Bullseye glibc: $BUSTER_GLIBC_VERSION"
# Build executable from Docker bullseye cmd="docker run --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --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'"
BULLSEYE_GLIBC_VERSION=$(docker run --network none --entrypoint /bin/bash --rm extract_otp_secrets:bullseye -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"') if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
echo "Bullseye glibc: $BULLSEYE_GLIBC_VERSION" eval "$cmd"
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:bullseye -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller -y --specpath installer --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'" cmd="dist/extract_otp_secrets_linux_x86_64 -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="dist/extract_otp_secrets_linux_x86_64 -h" # create Windows win_file_version_info.txt
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi cmd="VERSION_STR=$(setuptools-git-versioning) VERSION_MAJOR=$(cut -d '.' -f 1 <<< "$(setuptools-git-versioning)") VERSION_MINOR=$(cut -d '.' -f 2 <<< "$(setuptools-git-versioning)") VERSION_PATCH=$(cut -d '.' -f 3 <<< "$(setuptools-git-versioning)") VERSION_BUILD=$(($(git rev-list --count $(git tag | sort -V -r | sed '1!d')..HEAD))) COPYRIGHT_YEARS='2020-2023' envsubst < installer/win_file_version_info_template.txt > build/win_file_version_info.txt"
eval "$cmd" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="dist/extract_otp_secrets_linux_x86_64 --qr ZBAR example_export.png" # create macOS extract_otp_secrets_macos.spec from extract_otp_secrets_macos_template.spec
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi cmd="VERSION_STR=$(setuptools-git-versioning) COPYRIGHT_YEARS='2020-2023' envsubst < installer/extract_otp_secrets_macos_template.spec > build/extract_otp_secrets_macos.spec"
eval "$cmd" if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
fi eval "$cmd"
if $build_arm; then # build linux/arm64
# build linux/arm64 # Build extract_otp_secrets (Debian Buster)
cmd="docker run --platform linux/arm64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:bullseye-arm64 -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller --specpath installer -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_arm64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'" cmd="docker buildx build --platform=linux/arm64 . -t extract_otp_secrets:buster --pull -f docker/Dockerfile --build-arg RUN_TESTS=false --build-arg BASE_IMAGE=python:3.11-slim-buster"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="PLATFORM='linux/arm64' && EXE='dist/extract_otp_secrets_linux_arm64' && docker run --platform \"\$PLATFORM\" --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:bullseye-arm64 -c \"\$EXE -V && \$EXE -h && \$EXE example_export.png && \$EXE - < example_export.txt && \$EXE --qr ZBAR example_export.png && \$EXE --qr QREADER example_export.png && \$EXE --qr QREADER_DEEP example_export.png && \$EXE --qr CV2 example_export.png && \$EXE --qr CV2_WECHAT example_export.png\"" cmd="docker run --pull always --rm --privileged multiarch/qemu-user-static --reset -p yes"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
fi
fi
if $build_nuitka_exe; then cmd="docker run --platform linux/arm64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c 'apt-get update && apt-get -y install binutils && pip install -U pip && pip install -U -r /files/requirements.txt && pip install pyinstaller && PYTHONHASHSEED=31 && pyinstaller --specpath installer -y --add-data /usr/local/__yolo_v3_qr_detector/:__yolo_v3_qr_detector/ --onefile --name extract_otp_secrets_linux_arm64 --distpath /files/dist/ /files/src/extract_otp_secrets.py'"
if $build_x86_64; then if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets -c 'apt-get update && apt-get -y install binutils build-essential patchelf && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_x86_64_bookworm_compiled /files/src/extract_otp_secrets.py'" eval "$cmd"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="build/docker/nuitka/extract_otp_secrets_linux_x86_64_bookworm_compiled -h" cmd="PLATFORM='linux/arm64' && EXE='dist/extract_otp_secrets_linux_arm64' && docker run --platform \"\$PLATFORM\" --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:buster -c \"\$EXE -V && \$EXE -h && \$EXE example_export.png && \$EXE - < example_export.txt && \$EXE --qr ZBAR example_export.png && \$EXE --qr QREADER example_export.png && \$EXE --qr QREADER_DEEP example_export.png && \$EXE --qr CV2 example_export.png && \$EXE --qr CV2_WECHAT example_export.png\""
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
cmd="build/docker/nuitka/extract_otp_secrets_linux_x86_64_bookworm_compiled --qr ZBAR example_export.png"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="cp build/docker/nuitka/extract_otp_secrets_linux_x86_64_bookworm_compiled dist/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
# Build executable from Docker bullseye
BULLSEYE_GLIBC_VERSION=$(docker run --network none --entrypoint /bin/bash --rm extract_otp_secrets:bullseye -c 'ldd --version | sed "1!d" | sed -E "s/.* ([[:digit:]]+\.[[:digit:]]+)$/\1/"')
echo "Bookworm glibc: $BULLSEYE_GLIBC_VERSION"
cmd="docker run --platform linux/amd64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:bullseye -c 'apt-get update && apt-get -y install binutils build-essential patchelf && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_x86_64_bullseye_compiled /files/src/extract_otp_secrets.py'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="build/docker/nuitka/extract_otp_secrets_linux_x86_64_bullseye_compiled -h"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="build/docker/nuitka/extract_otp_secrets_linux_x86_64_bullseye_compiled --qr ZBAR example_export.png"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="cp build/docker/nuitka/extract_otp_secrets_linux_x86_64_bullseye_compiled dist/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
if $build_arm; then
# build linux/arm64
cmd="docker run --platform linux/arm64 --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:bullseye-arm64 -c 'apt-get update && apt-get -y install binutils build-essential patchelf qt5-default && pip install -U pip && pip install -U -r /files/requirements.txt && pip install nuitka pyqt5 && PYTHONHASHSEED=31 && python -m nuitka --enable-plugin=tk-inter --enable-plugin=pyqt5 --include-data-dir=/usr/local/__yolo_v3_qr_detector/=__yolo_v3_qr_detector/ --onefile --output-dir=/files/build/docker/nuitka --output-filename=extract_otp_secrets_linux_arm64_compiled /files/src/extract_otp_secrets.py'"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="PLATFORM='linux/arm64' && EXE='build/docker/nuitka/extract_otp_secrets_linux_arm64_compiled' && docker run --platform \"\$PLATFORM\" --entrypoint /bin/bash --rm -v \"$(pwd)\":/files -w /files extract_otp_secrets:bullseye-arm64 -c \"\$EXE -V && \$EXE -h && \$EXE example_export.png && \$EXE - < example_export.txt && \$EXE --qr ZBAR example_export.png && \$EXE --qr QREADER example_export.png && \$EXE --qr QREADER_DEEP example_export.png && \$EXE --qr CV2 example_export.png && \$EXE --qr CV2_WECHAT example_export.png\""
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
cmd="cp build/docker/nuitka/extract_otp_secrets_linux_arm64_compiled dist/"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd"
fi
fi fi
# Run GUI from Docker # Run GUI from Docker
if $build_x86_64 && $run_gui; then if $run_gui; then
cmd="docker run --network none --rm -v "$(pwd)":/files:ro --device=\"/dev/video0:/dev/video0\" --env=\"DISPLAY\" -v /tmp/.X11-unix:/tmp/.X11-unix:ro extract_otp_secrets &" cmd="docker run --rm -v "$(pwd)":/files:ro --device=\"/dev/video0:/dev/video0\" --env=\"DISPLAY\" -v /tmp/.X11-unix:/tmp/.X11-unix:ro extract_otp_secrets &"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
fi fi
@@ -658,18 +517,14 @@ if $run_gui; then
eval "$cmd" eval "$cmd"
fi fi
if $build_base; then line=$(printf '#%.0s' $(eval echo {1..$(( ($COLUMNS - 10) / 2))}))
line=$(printf '#%.0s' $(eval echo {1..$(( ($COLUMNS - 10) / 2))})) echo -e "\n${blueBold}$line RESULTS $line${reset}"
echo -e "\n${blueBold}$line RESULTS $line${reset}"
cmd="cat $TYPE_CHECK_OUT_FILE $LINT_OUT_FILE $COVERAGE_OUT_FILE" cmd="cat $TYPE_CHECK_OUT_FILE $LINT_OUT_FILE $COVERAGE_OUT_FILE"
if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi if $interactive ; then askContinueYn "$cmd"; else echo -e "${cyan}$cmd${reset}";fi
eval "$cmd" eval "$cmd"
fi
line=$(printf '#%.0s' $(eval echo {1..$(( ($COLUMNS - 10) / 2))})) line=$(printf '#%.0s' $(eval echo {1..$(( ($COLUMNS - 10) / 2))}))
echo -e "\n${greenBold}$line SUCCESS $line${reset}" echo -e "\n${greenBold}$line SUCCESS $line${reset}"
git status
quit quit

View File

@@ -1,122 +0,0 @@
{
"nodes": {
"devenv": {
"locked": {
"dir": "src/modules",
"lastModified": 1726050924,
"owner": "cachix",
"repo": "devenv",
"rev": "cf471f691c3765ed431199f61b8bd70530bc4305",
"treeHash": "04a5d566820f8fb1955c7c49ccbd3ff95d9129d7",
"type": "github"
},
"original": {
"dir": "src/modules",
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"treeHash": "2addb7b71a20a25ea74feeaf5c2f6a6b30898ecb",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"treeHash": "ca14199cabdfe1a06a7b1654c76ed49100a689f9",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1716977621,
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "4267e705586473d3e5c8d50299e71503f16a6fb6",
"treeHash": "6d9f1f7ca0faf1bc2eeb397c78a49623260d3412",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1725826545,
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f4c846aee8e1e29062aa8514d5e0ab270f4ec2f9",
"treeHash": "8fc49deaed3f2728a7147c38163cc468a117570a",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": {
"inputs": {
"flake-compat": "flake-compat",
"gitignore": "gitignore",
"nixpkgs": [
"nixpkgs"
],
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1725513492,
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "7570de7b9b504cfe92025dd1be797bf546f66528",
"treeHash": "4b46d77870afecd8f642541cb4f4927326343b59",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"nixpkgs": "nixpkgs",
"pre-commit-hooks": "pre-commit-hooks"
}
}
},
"root": "root",
"version": 7
}

View File

@@ -1,24 +0,0 @@
{
pkgs,
lib,
config,
inputs,
...
}:
{
cachix.enable = false;
languages.python = {
enable = true;
venv = {
enable = true;
requirements = ./requirements.txt;
};
};
packages = [
pkgs.git
pkgs.zbar
];
}

View File

@@ -1,14 +0,0 @@
inputs:
nixpkgs:
url: github:cachix/devenv-nixpkgs/rolling
# If you're using non-OSS software, you can set allowUnfree to true.
# allowUnfree: true
# If you're willing to use a package that's vulnerable
# permittedInsecurePackages:
# - "openssl-1.1.1w"
# If you have more than one devenv you can merge them
#imports:
# - ./backend

View File

@@ -1,5 +1,5 @@
# --build-arg BASE_IMAGE=python:3.11-slim-buster # --build-arg BASE_IMAGE=python:3.11-slim-buster
ARG BASE_IMAGE=python:3.13-slim-bookworm ARG BASE_IMAGE=python:3.11-slim-bullseye
FROM $BASE_IMAGE FROM $BASE_IMAGE
# https://docs.docker.com/engine/reference/builder/ # https://docs.docker.com/engine/reference/builder/
@@ -16,15 +16,14 @@ COPY requirements*.txt src/ run_pytest.sh pytest.ini tests/ example_*.txt exampl
ARG RUN_TESTS=true ARG RUN_TESTS=true
RUN uname -a \ RUN apt-get update && apt-get install -y --no-install-recommends \
&& apt-get update && apt-get install -y --no-install-recommends \
libgl1 \ libgl1 \
libglib2.0-0 \ libglib2.0-0 \
libsm6 \ libsm6 \
libzbar0 \ libzbar0 \
python3-tk \ python3-tk \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir -U pip -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 \
&& echo 'test -s /extract/.alias && . /extract/.alias || true' >> ~/.bashrc && echo 'test -s /extract/.alias && . /extract/.alias || true' >> ~/.bashrc
@@ -32,6 +31,6 @@ WORKDIR /files
ENTRYPOINT ["python", "/extract/extract_otp_secrets.py"] ENTRYPOINT ["python", "/extract/extract_otp_secrets.py"]
LABEL org.opencontainers.image.source=https://github.com/scito/extract_otp_secrets LABEL org.opencontainers.image.source https://github.com/scito/extract_otp_secrets
LABEL org.opencontainers.image.license=GPL-3.0+ LABEL org.opencontainers.image.license GPL-3.0+
LABEL maintainer="Scito https://scito.ch, https://github.com/scito" LABEL maintainer="Scito https://scito.ch, https://github.com/scito"

View File

@@ -1,4 +1,4 @@
ARG BASE_IMAGE=python:3.13-alpine ARG BASE_IMAGE=python:3.11-alpine
FROM $BASE_IMAGE FROM $BASE_IMAGE
# https://docs.docker.com/engine/reference/builder/ # https://docs.docker.com/engine/reference/builder/
@@ -17,8 +17,7 @@ COPY requirements*.txt src/ run_pytest.sh pytest.ini tests/ example_*.txt exampl
ARG RUN_TESTS=true ARG RUN_TESTS=true
RUN uname -a \ RUN apk add --no-cache \
&& apk add --no-cache \
jpeg \ jpeg \
zlib \ zlib \
&& echo "Arch: $(apk --print-arch)" \ && echo "Arch: $(apk --print-arch)" \
@@ -31,7 +30,6 @@ RUN uname -a \
zlib-dev \ zlib-dev \
; fi \ ; fi \
&& pip install --no-cache-dir -U \ && pip install --no-cache-dir -U \
pip \
colorama \ colorama \
Pillow \ Pillow \
protobuf \ protobuf \
@@ -44,6 +42,6 @@ WORKDIR /files
ENTRYPOINT ["python", "/extract/extract_otp_secrets.py"] ENTRYPOINT ["python", "/extract/extract_otp_secrets.py"]
LABEL org.opencontainers.image.source=https://github.com/scito/extract_otp_secrets LABEL org.opencontainers.image.source https://github.com/scito/extract_otp_secrets
LABEL org.opencontainers.image.license=GPL-3.0+ LABEL org.opencontainers.image.license GPL-3.0+
LABEL maintainer="Scito https://scito.ch, https://github.com/scito" LABEL maintainer="Scito https://scito.ch, https://github.com/scito"

View File

@@ -13,7 +13,7 @@ Generate from file: README.md
- [Installation of optional shared system libraries (recommended)](#installation-of-optional-shared-system-libraries-recommended) - [Installation of optional shared system libraries (recommended)](#installation-of-optional-shared-system-libraries-recommended)
- [Program help: arguments and options](#program-help-arguments-and-options) - [Program help: arguments and options](#program-help-arguments-and-options)
- [Examples](#examples) - [Examples](#examples)
- [Printing otp secrets from text file](#printing-otp-secrets-from-text-file) - [Printing otp secrets form text file](#printing-otp-secrets-form-text-file)
- [Printing otp secrets from image file](#printing-otp-secrets-from-image-file) - [Printing otp secrets from image file](#printing-otp-secrets-from-image-file)
- [Writing otp secrets to csv file](#writing-otp-secrets-to-csv-file) - [Writing otp secrets to csv file](#writing-otp-secrets-to-csv-file)
- [Writing otp secrets to json file](#writing-otp-secrets-to-json-file) - [Writing otp secrets to json file](#writing-otp-secrets-to-json-file)
@@ -33,8 +33,7 @@ Generate from file: README.md
- [pipenv](#pipenv) - [pipenv](#pipenv)
- [Visual Studio Code Remote - Containers / VSCode devcontainer](#visual-studio-code-remote---containers--vscode-devcontainer) - [Visual Studio Code Remote - Containers / VSCode devcontainer](#visual-studio-code-remote---containers--vscode-devcontainer)
- [venv](#venv) - [venv](#venv)
- [devbox 1](#devbox-1) - [devbox](#devbox)
- [devbox 2](#devbox-2)
- [docker](#docker) - [docker](#docker)
- [More docker examples](#more-docker-examples) - [More docker examples](#more-docker-examples)
- [Tests](#tests) - [Tests](#tests)
@@ -53,6 +52,5 @@ Generate from file: README.md
- [Problems and Troubleshooting](#problems-and-troubleshooting) - [Problems and Troubleshooting](#problems-and-troubleshooting)
- [Windows error message](#windows-error-message) - [Windows error message](#windows-error-message)
- [Related projects](#related-projects) - [Related projects](#related-projects)
- [Third party documentation](#third-party-documentation)
Table of contents generated. Table of contents generated.

View File

@@ -1,3 +0,0 @@
## Star History
[![Star History Chart](https://api.star-history.com/svg?repos=scito/extract_otp_secrets&type=Date)](https://star-history.com/#scito/extract_otp_secrets&Date)

View File

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

View File

@@ -1,11 +1,8 @@
[build-system] [build-system]
requires = [ requires = [
"pip", "setuptools>=64.0.0", "wheel>=0.37.0", "pip",
"nuitka",
# https://setuptools-git-versioning.readthedocs.io/en/latest/differences.html # https://setuptools-git-versioning.readthedocs.io/en/latest/differences.html
"setuptools>=64.0.0",
"setuptools-git-versioning", "setuptools-git-versioning",
"wheel>=0.37.0",
] ]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
@@ -21,12 +18,11 @@ classifiers = [
"Topic :: Utilities", "Topic :: Utilities",
"Topic :: Security", "Topic :: Security",
"Topic :: Multimedia :: Graphics :: Capture :: Digital Camera", "Topic :: Multimedia :: Graphics :: Capture :: Digital Camera",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Intended Audience :: End Users/Desktop", "Intended Audience :: End Users/Desktop",
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Intended Audience :: System Administrators", "Intended Audience :: System Administrators",
@@ -42,11 +38,15 @@ classifiers = [
dependencies = [ dependencies = [
"colorama>=0.4.6", "colorama>=0.4.6",
"opencv-contrib-python", "opencv-contrib-python",
# "opencv-contrib-python<=4.7.0; sys_platform == 'darwin'",
"Pillow", "Pillow",
"protobuf", "protobuf",
"pyzbar", "pyzbar",
"qrcode", "qrcode",
"qreader<2.0.0", "qreader<2.0.0",
# workaround for PYTHON <= 3.7: compatibility
"typing_extensions; python_version<='3.7'",
"importlib_metadata; python_version<='3.7'",
] ]
description = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps such as 'Google Authenticator'" description = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps such as 'Google Authenticator'"
dynamic = ["version"] dynamic = ["version"]
@@ -55,7 +55,7 @@ license = {text = "GNU General Public License v3 (GPLv3)"}
readme = "README.md" readme = "README.md"
authors = [{name = "scito", email = "info@scito.ch"}] authors = [{name = "scito", email = "info@scito.ch"}]
maintainers = [{name = "scito", email = "info@scito.ch"}] maintainers = [{name = "scito", email = "info@scito.ch"}]
requires-python = ">=3.8, <4" requires-python = ">=3.7, <4"
scripts = {extract_otp_secrets = "extract_otp_secrets:sys_main"} scripts = {extract_otp_secrets = "extract_otp_secrets:sys_main"}
urls = {Project-URL = "https://github.com/scito/extract_otp_secrets", Bug-Reports = "https://github.com/scito/extract_otp_secrets/issues", Source = "https://github.com/scito/extract_otp_secrets"} urls = {Project-URL = "https://github.com/scito/extract_otp_secrets", Bug-Reports = "https://github.com/scito/extract_otp_secrets/issues", Source = "https://github.com/scito/extract_otp_secrets"}

View File

@@ -3,7 +3,6 @@ flake8
gfm-toc gfm-toc
mypy mypy
mypy-protobuf mypy-protobuf
nuitka
pyinstaller pyinstaller
pylint pylint
pytest pytest

View File

@@ -1,7 +1,10 @@
colorama>=0.4.6 colorama>=0.4.6
importlib_metadata; python_version<='3.7'
opencv-contrib-python opencv-contrib-python
# opencv-contrib-python<=4.7.0; sys_platform == 'darwin'
Pillow Pillow
protobuf protobuf
pyzbar pyzbar
qrcode qrcode
qreader<2.0.0 qreader<2.0.0
typing_extensions; python_version<='3.7'

View File

@@ -2,7 +2,7 @@
name = extract_otp_secrets name = extract_otp_secrets
[options] [options]
python_requires = >=3.8, <4 python_requires = >=3.7, <4
py_modules = extract_otp_secrets, protobuf_generated_python.google_auth_pb2 py_modules = extract_otp_secrets, protobuf_generated_python.google_auth_pb2
package_dir = package_dir =
=src =src

View File

@@ -36,7 +36,6 @@ import argparse
import base64 import base64
import csv import csv
import fileinput import fileinput
import glob
import json import json
import os import os
import platform import platform
@@ -44,24 +43,35 @@ import re
import sys import sys
import urllib.parse as urlparse import urllib.parse as urlparse
from enum import Enum, IntEnum from enum import Enum, IntEnum
from importlib.metadata import PackageNotFoundError, version from typing import Any, List, Optional, Sequence, TextIO, Tuple, Union, TYPE_CHECKING
from typing import (Any, Final, List, Optional, Sequence, TextIO, Tuple,
TypedDict, Union)
import colorama import colorama
from qrcode import QRCode from pkg_resources import DistributionNotFound, get_distribution
from qrcode import QRCode # type: ignore
import protobuf_generated_python.google_auth_pb2 as pb import protobuf_generated_python.google_auth_pb2 as pb
# workaround for PYTHON <= 3.7: compatibility
if sys.version_info >= (3, 8):
from typing import Final, TypedDict
else:
from typing_extensions import Final, TypedDict
# workaround for PYTHON <= 3.7: compatibility
if sys.version_info >= (3, 8):
from importlib.metadata import PackageNotFoundError, version
else:
from importlib_metadata import PackageNotFoundError, version
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:]
quiet = '-q' in sys.argv[1:] or '--quiet' in sys.argv[1:] quiet = '-q' in sys.argv[1:] or '--quiet' in sys.argv[1:]
headless: bool = False headless: bool = False
try: try:
import cv2 import cv2 # type: ignore # TODO use cv2 types if available
import numpy as np import numpy as np # TODO use numpy types if available
import cv2.typing
try: try:
import tkinter import tkinter
@@ -77,8 +87,9 @@ try:
except Exception as e: except Exception as e:
if not quiet: if not quiet:
print(f""" print(f"""
WARN: Cannot import pyzbar module. This problem is probably due to the missing zbar shared library. (The zbar library is optional.) WARN: Cannot import pyzbar or qreader module. This problem is probably due to the missing zbar shared library.
See in README.md for the installation of the zbar shared library. On Linux and macOS libzbar0 must be installed.
See in README.md for the installation of the libzbar0.
Exception: {e}\n""", file=sys.stderr) Exception: {e}\n""", file=sys.stderr)
zbar_available = False zbar_available = False
if debug_mode: if debug_mode:
@@ -94,8 +105,9 @@ Exception: {e}\n""", file=sys.stderr)
FONT_SCALE: Final[float] = 1.3 FONT_SCALE: Final[float] = 1.3
FONT_THICKNESS: Final[int] = 1 FONT_THICKNESS: Final[int] = 1
FONT_LINE_STYLE: Final[int] = cv2.LINE_AA FONT_LINE_STYLE: Final[int] = cv2.LINE_AA
FONT_COLOR: Final[ColorBGR] = 255, 0, 0 FONT_COLOR: Final[ColorBGR] = (255, 0, 0)
BOX_THICKNESS: Final[int] = 5 BOX_THICKNESS: Final[int] = 5
# workaround for PYTHON <= 3.7: must use () for assignments
WINDOW_X: Final[int] = 0 WINDOW_X: Final[int] = 0
WINDOW_Y: Final[int] = 1 WINDOW_Y: Final[int] = 1
WINDOW_WIDTH: Final[int] = 2 WINDOW_WIDTH: Final[int] = 2
@@ -104,10 +116,10 @@ Exception: {e}\n""", file=sys.stderr)
TEXT_HEIGHT: Final[int] = 1 TEXT_HEIGHT: Final[int] = 1
BORDER: Final[int] = 5 BORDER: Final[int] = 5
START_Y: Final[int] = 20 START_Y: Final[int] = 20
START_POS_TEXT: Final[Point] = BORDER, START_Y START_POS_TEXT: Final[Point] = (BORDER, START_Y)
NORMAL_COLOR: Final[ColorBGR] = 255, 0, 255 NORMAL_COLOR: Final[ColorBGR] = (255, 0, 255)
SUCCESS_COLOR: Final[ColorBGR] = 0, 255, 0 SUCCESS_COLOR: Final[ColorBGR] = (0, 255, 0)
FAILURE_COLOR: Final[ColorBGR] = 0, 0, 255 FAILURE_COLOR: Final[ColorBGR] = (0, 0, 255)
CHAR_DX: Final[int] = (lambda text: cv2.getTextSize(text, FONT, FONT_SCALE, FONT_THICKNESS)[0][TEXT_WIDTH] // len(text))("28 QR codes capturedMMM") CHAR_DX: Final[int] = (lambda text: cv2.getTextSize(text, FONT, FONT_SCALE, FONT_THICKNESS)[0][TEXT_WIDTH] // len(text))("28 QR codes capturedMMM")
FONT_DY: Final[int] = cv2.getTextSize("M", FONT, FONT_SCALE, FONT_THICKNESS)[0][TEXT_HEIGHT] + 5 FONT_DY: Final[int] = cv2.getTextSize("M", FONT, FONT_SCALE, FONT_THICKNESS)[0][TEXT_HEIGHT] + 5
WINDOW_NAME: Final[str] = "Extract OTP Secrets: Capture QR Codes from Camera" WINDOW_NAME: Final[str] = "Extract OTP Secrets: Capture QR Codes from Camera"
@@ -120,12 +132,12 @@ except ImportError as e:
if debug_mode: if debug_mode:
raise e raise e
# Workaround for PYTHON <= 3.9: Generally Union[int, None] used instead of int | None # Workaround for PYTHON <= 3.9: Union[int, None] used instead of int | None
# Types # Types
Args = argparse.Namespace Args = argparse.Namespace
OtpUrl = str OtpUrl = str
# Workaround for PYTHON <= 3.9: Otp = TypedDict('Otp', {'name': str, 'secret': str, 'issuer': str, 'type': str, 'counter': int | None, 'url': OtpUrl}) # workaround for 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}) Otp = TypedDict('Otp', {'name': str, 'secret': str, 'issuer': str, 'type': str, 'counter': Union[int, None], 'url': OtpUrl})
# workaround for PYTHON <= 3.9: Otps = list[Otp] # workaround for PYTHON <= 3.9: Otps = list[Otp]
Otps = List[Otp] Otps = List[Otp]
@@ -189,7 +201,6 @@ def main(sys_args: list[str]) -> None:
write_keepass_csv(args.keepass, otps) write_keepass_csv(args.keepass, otps)
write_json(args.json, otps) write_json(args.json, otps)
write_txt(args.txt, otps, True) write_txt(args.txt, otps, True)
write_urls(args.urls, otps)
# workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None # workaround for PYTHON <= 3.9 use: pb.MigrationPayload | None
@@ -266,7 +277,9 @@ def extract_otp_from_otp_url(otpauth_migration_url: str, otps: Otps, urls_count:
def parse_args(sys_args: list[str]) -> Args: def parse_args(sys_args: list[str]) -> Args:
global verbose, quiet, colored global verbose, quiet, colored
cmd = f"python {name}" if (name := os.path.basename(sys.argv[0])).endswith('.py') else f"{name}" # For PYTHON <= 3.7: Use :=
name = os.path.basename(sys.argv[0])
cmd = f"python {name}" if name.endswith('.py') else f"{name}"
description_text = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps" description_text = "Extracts one time password (OTP) secrets from QR codes exported by two-factor authentication (2FA) apps"
if cv2_available: if cv2_available:
description_text += "\nIf no infiles are provided, a GUI window starts and QR codes are captured from the camera." description_text += "\nIf no infiles are provided, a GUI window starts and QR codes are captured from the camera."
@@ -282,11 +295,10 @@ def parse_args(sys_args: list[str]) -> Args:
epilog=example_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; 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 cv2_available else '+') b) image file containing a QR code or = for stdin for an image containing a QR code""", nargs='*' if cv2_available else '+')
arg_parser.add_argument('--csv', '-c', help='export csv file, or - for stdout', metavar=('FILE')) arg_parser.add_argument('--csv', '-c', help='export csv file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--keepass', '-k', help='export totp/hotp csv file(s) for KeePass, - for stdout', metavar=('FILE')) arg_parser.add_argument('--keepass', '-k', help='export totp/hotp csv file(s) for KeePass, - for stdout', metavar=('FILE'))
arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE')) arg_parser.add_argument('--json', '-j', help='export json file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--txt', '-t', help='export txt file or - for stdout', metavar=('FILE')) arg_parser.add_argument('--txt', '-t', help='export txt file or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--urls', '-u', help='export file with list of otpauth urls, or - for stdout', metavar=('FILE'))
arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal', action='store_true') arg_parser.add_argument('--printqr', '-p', help='print QR code(s) as text to the terminal', action='store_true')
arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to directory', metavar=('DIR')) arg_parser.add_argument('--saveqr', '-s', help='save QR code(s) as images to directory', metavar=('DIR'))
if cv2_available: if cv2_available:
@@ -304,14 +316,13 @@ b) image file containing a QR code or = for stdin for an image containing a QR c
output_group.add_argument('-q', '--quiet', help='no stdout output, except output set by -', action='store_true') output_group.add_argument('-q', '--quiet', help='no stdout output, except output set by -', action='store_true')
args = arg_parser.parse_args(sys_args) args = arg_parser.parse_args(sys_args)
colored = not args.no_color colored = not args.no_color
if args.csv == '-' or args.json == '-' or args.keepass == '-' or args.txt == '-' or args.urls == '-': if args.csv == '-' or args.json == '-' or args.keepass == '-' or args.txt == '-':
args.quiet = args.q = True args.quiet = args.q = True
verbose = args.verbose if args.verbose else LogLevel.NORMAL verbose = args.verbose if args.verbose else LogLevel.NORMAL
if args.debug: if args.debug:
verbose = LogLevel.DEBUG verbose = LogLevel.DEBUG
log_debug('Debug mode start') log_debug('Debug mode start')
log_debug(args)
quiet = True if args.quiet else False quiet = True if args.quiet else False
if verbose: print(f"QReader installed: {cv2_available}") if verbose: print(f"QReader installed: {cv2_available}")
if cv2_available: if cv2_available:
@@ -382,7 +393,7 @@ def extract_otps_from_camera(args: Args) -> Otps:
cv2_print_text(img, f"Mode: {qr_mode.name} (Hit SPACE to change)", 0, TextPosition.LEFT, FONT_COLOR, 20) cv2_print_text(img, f"Mode: {qr_mode.name} (Hit SPACE to change)", 0, TextPosition.LEFT, FONT_COLOR, 20)
cv2_print_text(img, "Press ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17) cv2_print_text(img, "Press ESC to quit", 1, TextPosition.LEFT, FONT_COLOR, 17)
cv2_print_text(img, "Press c,j,k,t,u to save as csv/json/keepass/txt/urls file", 2, TextPosition.LEFT, FONT_COLOR, None) cv2_print_text(img, "Press C/J/K/T to save as csv/json/keepass/txt file", 2, TextPosition.LEFT, FONT_COLOR, None)
cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR) cv2_print_text(img, f"{len(otp_urls)} QR code{'s'[:len(otp_urls) != 1]} captured", 0, TextPosition.RIGHT, FONT_COLOR)
cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR) cv2_print_text(img, f"{len(otps)} otp{'s'[:len(otps) != 1]} extracted", 1, TextPosition.RIGHT, FONT_COLOR)
@@ -409,15 +420,16 @@ def get_color(new_otps_count: int, otp_url: str) -> ColorBGR:
return NORMAL_COLOR return NORMAL_COLOR
# TODO use proper cv2 types if available # TODO use cv2 types if available
def cv2_draw_box(img: cv2.typing.MatLike, raw_pts: cv2.typing.MatLike | list[tuple[Any, Any]], color: ColorBGR) -> np.ndarray[Any, np.dtype[np.int32]]: def cv2_draw_box(img: Any, raw_pts: Any, color: ColorBGR) -> Any:
pts = np.array([raw_pts], np.int32) pts = np.array([raw_pts], np.int32)
pts = pts.reshape((-1, 1, 2)) pts = pts.reshape((-1, 1, 2))
cv2.polylines(img, [pts], True, color, BOX_THICKNESS) cv2.polylines(img, [pts], True, color, BOX_THICKNESS)
return pts return pts
def cv2_print_text(img: cv2.typing.MatLike, text: str, line_number: int, position: TextPosition, color: ColorBGR, opposite_len: Optional[int] = None) -> None: # TODO use cv2 types if available
def cv2_print_text(img: Any, text: str, line_number: int, position: TextPosition, color: ColorBGR, opposite_len: Optional[int] = None) -> None:
window_dim = cv2.getWindowImageRect(WINDOW_NAME) window_dim = cv2.getWindowImageRect(WINDOW_NAME)
out_text = text out_text = text
if opposite_len: if opposite_len:
@@ -488,18 +500,6 @@ def cv2_handle_pressed_keys(qr_mode: QRMode, otps: Otps) -> Tuple[bool, QRMode]:
tk_root.update() tk_root.update()
if len(file_name) > 0: if len(file_name) > 0:
write_txt(file_name, otps, True) write_txt(file_name, otps, True)
elif (key == ord('u') or key == ord('U')) and is_not_headless():
if has_no_otps_show_warning(otps):
pass
else:
file_name = tkinter.filedialog.asksaveasfilename(
title="Save extracted otp secrets as list of urls",
defaultextension='.txt',
filetypes=[('Text', '*.txt'), ('All', '*.*')]
)
tk_root.update()
if len(file_name) > 0:
write_urls(file_name, otps)
elif key == 32: elif key == 32:
qr_mode = next_valid_qr_mode(qr_mode, zbar_available) qr_mode = next_valid_qr_mode(qr_mode, zbar_available)
if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}") if verbose >= LogLevel.MORE_VERBOSE: print(f"QR reading mode: {qr_mode}")
@@ -528,20 +528,14 @@ def extract_otps_from_files(args: Args) -> Otps:
files_count = urls_count = otps_count = 0 files_count = urls_count = otps_count = 0
if verbose: print(f"Input files: {args.infile}") if verbose: print(f"Input files: {args.infile}")
for infile_raw in args.infile: for infile in args.infile:
expanded_infiles = glob.glob(infile_raw) if verbose >= LogLevel.MORE_VERBOSE: log_verbose(f"Processing infile {infile}")
if not expanded_infiles: files_count += 1
expanded_infiles = [infile_raw] for line in get_otp_urls_from_file(infile, args):
if verbose >= LogLevel.DEBUG: log_debug(f"Could not expand input files, fallback to infile {infile_raw}") if verbose >= LogLevel.MORE_VERBOSE: log_verbose(line)
if verbose >= LogLevel.DEBUG: log_debug(f"Expanded input files: {expanded_infiles}") if line.startswith('#') or line == '': continue
for infile in expanded_infiles: urls_count += 1
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(f"Processing infile {infile}") otps_count += extract_otp_from_otp_url(line, otps, urls_count, infile, args)
files_count += 1
for line in get_otp_urls_from_file(infile, args):
if verbose >= LogLevel.MORE_VERBOSE: log_verbose(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"Extracted {otps_count} otp{'s'[:otps_count != 1]} from {urls_count} otp url{'s'[:urls_count != 1]} by reading {files_count} infile{'s'[:files_count != 1]}") if verbose: print(f"Extracted {otps_count} otp{'s'[:otps_count != 1]} from {urls_count} otp url{'s'[:urls_count != 1]} by reading {files_count} infile{'s'[:files_count != 1]}")
return otps return otps
@@ -599,7 +593,7 @@ def convert_img_to_otp_urls(filename: str, args: Args) -> OtpUrls:
stdin = sys.stdin.buffer.read() stdin = sys.stdin.buffer.read()
except AttributeError: except AttributeError:
# Workaround for pytest, since pytest cannot monkeypatch sys.stdin.buffer # Workaround for pytest, since pytest cannot monkeypatch sys.stdin.buffer
stdin = sys.stdin.read() stdin = sys.stdin.read() # type: ignore # Workaround for pytest fixtures
if not stdin: if not stdin:
log_warn("stdin is empty") log_warn("stdin is empty")
try: try:
@@ -686,10 +680,6 @@ def print_otp(otp: Otp, out: Optional[TextIO] = None) -> None:
print(otp['url'], file=out) print(otp['url'], file=out)
def write_url(otp: Otp, out: Optional[TextIO] = None) -> None:
print(otp['url'], file=out)
def save_qr_image(otp: Otp, dir: str, j: int) -> str: def save_qr_image(otp: Otp, dir: str, j: int) -> str:
if not (os.path.exists(dir)): os.makedirs(dir, exist_ok=True) if not (os.path.exists(dir)): os.makedirs(dir, exist_ok=True)
pattern = re.compile(r'[\W_]+') pattern = re.compile(r'[\W_]+')
@@ -704,7 +694,7 @@ def save_qr_image_file(otp_url: OtpUrl, name: str) -> None:
qr.add_data(otp_url) qr.add_data(otp_url)
img = qr.make_image(fill_color='black', back_color='white') img = qr.make_image(fill_color='black', back_color='white')
if verbose: print(f"Saving to {name}") if verbose: print(f"Saving to {name}")
img.save(name) # type: ignore img.save(name)
def print_qr(otp_url: str, out: Optional[TextIO] = None) -> None: def print_qr(otp_url: str, out: Optional[TextIO] = None) -> None:
@@ -723,14 +713,6 @@ def write_txt(file: str, otps: Otps, write_qr: bool = False) -> None:
print(file=outfile) print(file=outfile)
def write_urls(file: str, otps: Otps) -> None:
if file and len(file) > 0 and len(otps) > 0:
with open_file_or_stdout(file) as outfile:
for otp in otps:
write_url(otp, outfile)
if not quiet: print(f"Exported {len(otps)} otp{'s'[:len(otps) != 1]} to otpauth url list file {file}")
def write_csv(file: str, otps: Otps) -> None: def write_csv(file: str, otps: Otps) -> None:
if file and len(file) > 0 and len(otps) > 0: if file and len(file) > 0 and len(otps) > 0:
with open_file_or_stdout_for_csv(file) as outfile: with open_file_or_stdout_for_csv(file) as outfile:
@@ -754,8 +736,8 @@ def write_keepass_csv(file: str, otps: Otps) -> None:
if has_hotp: if has_hotp:
count_hotp_entries = write_keepass_htop_csv(otp_filename_hotp, otps) count_hotp_entries = write_keepass_htop_csv(otp_filename_hotp, otps)
if not quiet: if not quiet:
if has_totp and count_totp_entries: print(f"Exported {count_totp_entries} totp entrie{'s'[:count_totp_entries != 1]} to keepass csv file {otp_filename_totp}") if count_totp_entries: print(f"Exported {count_totp_entries} totp entrie{'s'[:count_totp_entries != 1]} to keepass csv file {otp_filename_totp}")
if has_hotp and 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(file: str, otps: Otps) -> int: def write_keepass_totp_csv(file: str, otps: Otps) -> int:
@@ -917,20 +899,27 @@ def get_raw_version() -> str:
return __version__ return __version__
except PackageNotFoundError: except PackageNotFoundError:
# package is not installed # package is not installed
return '' pass
# In some cases importlib cannot properly detect package version, for example it was compiled into executable file, so it uses some custom import mechanism.
# Instead, use pkg_resources which is included in setuptools (but has a significant runtime cost)
try:
__version__ = get_distribution("package-name").version
return __version__
except DistributionNotFound:
# package is not installed
pass
return ''
# workaround for PYTHON <= 3.9 use: BaseException | None # workaround for PYTHON <= 3.9 use: BaseException | None
def log_debug(*values: object, sep: Optional[str] = ' ') -> None: def log_debug(*values: object, sep: Optional[str] = ' ') -> None:
if os.name == 'nt':
# Workaround "Windows fatal exception: access violation"
print(f"\nDEBUG: {str(values[0])}")
return
if colored: if colored:
print(f"{colorama.Fore.CYAN}\nDEBUG: {str(values[0])}", *values[1:], colorama.Fore.RESET, sep=sep) print(f"{colorama.Fore.CYAN}\nDEBUG: {str(values[0])}", *values[1:], colorama.Fore.RESET, sep)
else: else:
print(f"\nDEBUG: {str(values[0])}", *values[1:], sep=sep) print(f"\nDEBUG: {str(values[0])}", *values[1:], sep)
# workaround for PYTHON <= 3.9 use: BaseException | None # workaround for PYTHON <= 3.9 use: BaseException | None

View File

@@ -1,22 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT! # Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: google_auth.proto # source: google_auth.proto
# Protobuf Python Version: 5.29.3
"""Generated protocol buffer code.""" """Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
5,
29,
3,
'',
'google_auth.proto'
)
# @@protoc_insertion_point(imports) # @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default() _sym_db = _symbol_database.Default()
@@ -29,8 +18,9 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11google_auth.pr
_globals = globals() _globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'google_auth_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS: if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._loaded_options = None
DESCRIPTOR._options = None
_globals['_MIGRATIONPAYLOAD']._serialized_start=22 _globals['_MIGRATIONPAYLOAD']._serialized_start=22
_globals['_MIGRATIONPAYLOAD']._serialized_end=461 _globals['_MIGRATIONPAYLOAD']._serialized_end=461
_globals['_MIGRATIONPAYLOAD_OTPPARAMETERS']._serialized_start=176 _globals['_MIGRATIONPAYLOAD_OTPPARAMETERS']._serialized_start=176

View File

@@ -2,7 +2,6 @@
@generated by mypy-protobuf. Do not edit manually! @generated by mypy-protobuf. Do not edit manually!
isort:skip_file isort:skip_file
""" """
import builtins import builtins
import collections.abc import collections.abc
import google.protobuf.descriptor import google.protobuf.descriptor
@@ -19,7 +18,7 @@ else:
DESCRIPTOR: google.protobuf.descriptor.FileDescriptor DESCRIPTOR: google.protobuf.descriptor.FileDescriptor
@typing.final @typing_extensions.final
class MigrationPayload(google.protobuf.message.Message): class MigrationPayload(google.protobuf.message.Message):
"""Copied from: https://github.com/beemdevelopment/Aegis/blob/master/app/src/main/proto/google_auth.proto""" """Copied from: https://github.com/beemdevelopment/Aegis/blob/master/app/src/main/proto/google_auth.proto"""
@@ -29,7 +28,7 @@ class MigrationPayload(google.protobuf.message.Message):
ValueType = typing.NewType("ValueType", builtins.int) ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType V: typing_extensions.TypeAlias = ValueType
class _AlgorithmEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._Algorithm.ValueType], builtins.type): class _AlgorithmEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._Algorithm.ValueType], builtins.type): # noqa: F821
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
ALGO_INVALID: MigrationPayload._Algorithm.ValueType # 0 ALGO_INVALID: MigrationPayload._Algorithm.ValueType # 0
ALGO_SHA1: MigrationPayload._Algorithm.ValueType # 1 ALGO_SHA1: MigrationPayload._Algorithm.ValueType # 1
@@ -42,7 +41,7 @@ class MigrationPayload(google.protobuf.message.Message):
ValueType = typing.NewType("ValueType", builtins.int) ValueType = typing.NewType("ValueType", builtins.int)
V: typing_extensions.TypeAlias = ValueType V: typing_extensions.TypeAlias = ValueType
class _OtpTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._OtpType.ValueType], builtins.type): class _OtpTypeEnumTypeWrapper(google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[MigrationPayload._OtpType.ValueType], builtins.type): # noqa: F821
DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor
OTP_INVALID: MigrationPayload._OtpType.ValueType # 0 OTP_INVALID: MigrationPayload._OtpType.ValueType # 0
OTP_HOTP: MigrationPayload._OtpType.ValueType # 1 OTP_HOTP: MigrationPayload._OtpType.ValueType # 1
@@ -53,7 +52,7 @@ class MigrationPayload(google.protobuf.message.Message):
OTP_HOTP: MigrationPayload.OtpType.ValueType # 1 OTP_HOTP: MigrationPayload.OtpType.ValueType # 1
OTP_TOTP: MigrationPayload.OtpType.ValueType # 2 OTP_TOTP: MigrationPayload.OtpType.ValueType # 2
@typing.final @typing_extensions.final
class OtpParameters(google.protobuf.message.Message): class OtpParameters(google.protobuf.message.Message):
DESCRIPTOR: google.protobuf.descriptor.Descriptor DESCRIPTOR: google.protobuf.descriptor.Descriptor
@@ -82,19 +81,19 @@ class MigrationPayload(google.protobuf.message.Message):
type: global___MigrationPayload.OtpType.ValueType = ..., type: global___MigrationPayload.OtpType.ValueType = ...,
counter: builtins.int = ..., counter: builtins.int = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.Literal["algorithm", b"algorithm", "counter", b"counter", "digits", b"digits", "issuer", b"issuer", "name", b"name", "secret", b"secret", "type", b"type"]) -> 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 OTP_PARAMETERS_FIELD_NUMBER: builtins.int
VERSION_FIELD_NUMBER: builtins.int VERSION_FIELD_NUMBER: builtins.int
BATCH_SIZE_FIELD_NUMBER: builtins.int BATCH_SIZE_FIELD_NUMBER: builtins.int
BATCH_INDEX_FIELD_NUMBER: builtins.int BATCH_INDEX_FIELD_NUMBER: builtins.int
BATCH_ID_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 version: builtins.int
batch_size: builtins.int batch_size: builtins.int
batch_index: builtins.int batch_index: builtins.int
batch_id: builtins.int batch_id: builtins.int
@property
def otp_parameters(self) -> google.protobuf.internal.containers.RepeatedCompositeFieldContainer[global___MigrationPayload.OtpParameters]: ...
def __init__( def __init__(
self, self,
*, *,
@@ -104,6 +103,6 @@ class MigrationPayload(google.protobuf.message.Message):
batch_index: builtins.int = ..., batch_index: builtins.int = ...,
batch_id: builtins.int = ..., batch_id: builtins.int = ...,
) -> None: ... ) -> None: ...
def ClearField(self, field_name: typing.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: ... 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 global___MigrationPayload = MigrationPayload

View File

@@ -1,5 +1,5 @@
QReader installed: True QReader installed: True
CV2 version: 4.10.0 CV2 version: 4.7.0
QR reading mode: ZBAR QR reading mode: ZBAR
Input files: ['example_export.txt'] Input files: ['example_export.txt']

View File

@@ -1,5 +1,5 @@
QReader installed: True QReader installed: True
CV2 version: 4.10.0 CV2 version: 4.7.0
QR reading mode: ZBAR QR reading mode: ZBAR
Input files: ['example_export.txt'] Input files: ['example_export.txt']

View File

@@ -1,12 +1,10 @@
QReader installed: True QReader installed: True
CV2 version: 4.10.0 CV2 version: 4.7.0
QR reading mode: ZBAR QR reading mode: ZBAR
Version: extract_otp_secrets 2.8.4.post4+git.7ce765dd.dirty Linux x86_64 Python 3.11.10 (CPython/called as script) Version: extract_otp_secrets 2.0.2.post50+git.158245dd.dirty Linux x86_64 Python 3.11.1 (CPython/called as script)
Input files: ['example_export.txt'] Input files: ['example_export.txt']
DEBUG: Expanded input files: ['example_export.txt']
Processing infile example_export.txt Processing infile example_export.txt
Reading lines of 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/ # 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
@@ -34,17 +32,16 @@ otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8O
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi # otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B', fragment='') DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B', fragment='')
DEBUG: querystring params={'data': ['CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B']} DEBUG: querystring params={'data': ['CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B']}
DEBUG: data_base64=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B DEBUG: data_base64=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B
DEBUG: data_base64_fixed=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B DEBUG: data_base64_fixed=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B
DEBUG: DEBUG:
1. Payload Line 1. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi" name: "pi@raspberrypi"
issuer: "raspberrypi" issuer: "raspberrypi"
@@ -55,11 +52,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -1320898453 batch_id: -1320898453
1. Secret 1. Secret
DEBUG: OTP enum type: OTP_TOTP DEBUG: OTP enum type: OTP_TOTP
Name: pi@raspberrypi Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi Issuer: raspberrypi
@@ -70,17 +68,16 @@ otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspber
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY # otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D', fragment='') DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D', fragment='')
DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=']} DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=']}
DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE= DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=
DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE= DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=
DEBUG: DEBUG:
2. Payload Line 2. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi" name: "pi@raspberrypi"
algorithm: ALGO_SHA1 algorithm: ALGO_SHA1
@@ -90,11 +87,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -2094403140 batch_id: -2094403140
2. Secret 2. Secret
DEBUG: OTP enum type: OTP_TOTP DEBUG: OTP enum type: OTP_TOTP
Name: pi@raspberrypi Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp Type: totp
@@ -105,17 +103,16 @@ otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY # otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B', fragment='') DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B', fragment='')
DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B']} DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B']}
DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B
DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B
DEBUG: DEBUG:
3. Payload Line 3. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi" name: "pi@raspberrypi"
algorithm: ALGO_SHA1 algorithm: ALGO_SHA1
@@ -133,11 +130,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -1822886384 batch_id: -1822886384
3. Secret 3. Secret
DEBUG: OTP enum type: OTP_TOTP DEBUG: OTP enum type: OTP_TOTP
Name: pi@raspberrypi Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp Type: totp
@@ -146,7 +144,7 @@ otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
4. Secret 4. Secret
DEBUG: OTP enum type: OTP_TOTP DEBUG: OTP enum type: OTP_TOTP
Name: pi@raspberrypi Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi Issuer: raspberrypi
@@ -157,17 +155,16 @@ otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspber
# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4 # otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D', fragment='') DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D', fragment='')
DEBUG: querystring params={'data': ['CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=']} DEBUG: querystring params={'data': ['CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=']}
DEBUG: data_base64=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE= DEBUG: data_base64=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=
DEBUG: data_base64_fixed=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE= DEBUG: data_base64_fixed=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=
DEBUG: DEBUG:
4. Payload Line 4. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "hotp demo" name: "hotp demo"
algorithm: ALGO_SHA1 algorithm: ALGO_SHA1
@@ -178,11 +175,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -1558849573 batch_id: -1558849573
5. Secret 5. Secret
DEBUG: OTP enum type: OTP_HOTP DEBUG: OTP enum type: OTP_HOTP
Name: hotp demo Name: hotp demo
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: hotp Type: hotp
@@ -194,17 +192,16 @@ otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
# Name: "encoding: ¿äÄéÉ? (demo)" # Name: "encoding: ¿äÄéÉ? (demo)"
otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D', fragment='') DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D', fragment='')
DEBUG: querystring params={'data': ['CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==']} DEBUG: querystring params={'data': ['CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==']}
DEBUG: data_base64=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ== DEBUG: data_base64=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==
DEBUG: data_base64_fixed=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ== DEBUG: data_base64_fixed=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==
DEBUG: DEBUG:
5. Payload Line 5. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "encoding: ¿äÄéÉ? (demo)" name: "encoding: ¿äÄéÉ? (demo)"
algorithm: ALGO_SHA1 algorithm: ALGO_SHA1
@@ -214,11 +211,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -171198419 batch_id: -171198419
6. Secret 6. Secret
DEBUG: OTP enum type: OTP_TOTP DEBUG: OTP enum type: OTP_TOTP
Name: encoding: ¿äÄéÉ? (demo) Name: encoding: ¿äÄéÉ? (demo)
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp Type: totp

View File

@@ -1,5 +1,5 @@
QReader installed: True QReader installed: True
CV2 version: 4.10.0 CV2 version: 4.7.0
QR reading mode: ZBAR QR reading mode: ZBAR
Input files: ['example_export.txt'] Input files: ['example_export.txt']

View File

@@ -1,5 +1,5 @@
QReader installed: True QReader installed: True
CV2 version: 4.10.0 CV2 version: 4.7.0
QR reading mode: ZBAR QR reading mode: ZBAR
Input files: ['example_export.txt'] Input files: ['example_export.txt']

View File

@@ -1,12 +1,10 @@
QReader installed: True QReader installed: True
CV2 version: 4.10.0 CV2 version: 4.7.0
QR reading mode: ZBAR QR reading mode: ZBAR
Version: extract_otp_secrets 2.8.4.post4+git.7ce765dd.dirty Linux x86_64 Python 3.11.10 (CPython/called as script) Version: extract_otp_secrets 2.0.2.post50+git.158245dd.dirty Linux x86_64 Python 3.11.1 (CPython/called as script)
Input files: ['example_export.txt'] Input files: ['example_export.txt']

DEBUG: Expanded input files: ['example_export.txt'] 
Processing infile example_export.txt Processing infile example_export.txt
Reading lines of 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/ # 2FA example from https://www.raspberrypi.org/blog/setting-up-two-factor-authentication-on-your-raspberry-pi/
@@ -34,17 +32,16 @@ Reading lines of example_export.txt
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi # otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B otpauth-migration://offline?data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B
 
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B', fragment='')  DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK%2B%2F%2F%2F%2F%2F8B', fragment='') 
 
DEBUG: querystring params={'data': ['CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B']}  DEBUG: querystring params={'data': ['CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B']} 
 
DEBUG: data_base64=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B  DEBUG: data_base64=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B 
 
DEBUG: data_base64_fixed=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B  DEBUG: data_base64_fixed=CjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACjr4JKK+/////8B 
 
DEBUG: DEBUG:
1. Payload Line 1. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi" name: "pi@raspberrypi"
issuer: "raspberrypi" issuer: "raspberrypi"
@@ -55,12 +52,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -1320898453 batch_id: -1320898453


1. Secret 1. Secret
 
DEBUG: OTP enum type: OTP_TOTP  DEBUG: OTP enum type: OTP_TOTP 
Name: pi@raspberrypi Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi Issuer: raspberrypi
@@ -71,17 +68,16 @@ otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspber
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY # otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D
 
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D', fragment='')  DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4%2F%2F%2F%2F%2FwE%3D', fragment='') 
 
DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=']}  DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=']} 
 
DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=  DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE= 
 
DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE=  DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACEAEYASAAKLzjp5n4/////wE= 
 
DEBUG: DEBUG:
2. Payload Line 2. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi" name: "pi@raspberrypi"
algorithm: ALGO_SHA1 algorithm: ALGO_SHA1
@@ -91,12 +87,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -2094403140 batch_id: -2094403140


2. Secret 2. Secret
 
DEBUG: OTP enum type: OTP_TOTP  DEBUG: OTP enum type: OTP_TOTP 
Name: pi@raspberrypi Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp Type: totp
@@ -107,17 +103,16 @@ otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
# otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY # otpauth://totp/pi@raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B otpauth-migration://offline?data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B
 
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B', fragment='')  DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa%2Bf%2F%2F%2F%2F8B', fragment='') 
 
DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B']}  DEBUG: querystring params={'data': ['CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B']} 
 
DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B  DEBUG: data_base64=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B 
 
DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B  DEBUG: data_base64_fixed=CigKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpIAEoATACCjUKEPqlBekzoNEukL7qlsjBCDYSDnBpQHJhc3BiZXJyeXBpGgtyYXNwYmVycnlwaSABKAEwAhABGAEgACiQ7OOa+f////8B 
 
DEBUG: DEBUG:
3. Payload Line 3. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "pi@raspberrypi" name: "pi@raspberrypi"
algorithm: ALGO_SHA1 algorithm: ALGO_SHA1
@@ -135,12 +130,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -1822886384 batch_id: -1822886384


3. Secret 3. Secret
 
DEBUG: OTP enum type: OTP_TOTP  DEBUG: OTP enum type: OTP_TOTP 
Name: pi@raspberrypi Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp Type: totp
@@ -149,7 +144,7 @@ otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
4. Secret 4. Secret
 
DEBUG: OTP enum type: OTP_TOTP  DEBUG: OTP enum type: OTP_TOTP 
Name: pi@raspberrypi Name: pi@raspberrypi
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Issuer: raspberrypi Issuer: raspberrypi
@@ -160,17 +155,16 @@ otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspber
# otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4 # otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D otpauth-migration://offline?data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D
 
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D', fragment='')  DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6%2F%2F%2F%2F%2FwE%3D', fragment='') 
 
DEBUG: querystring params={'data': ['CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=']}  DEBUG: querystring params={'data': ['CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=']} 
 
DEBUG: data_base64=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=  DEBUG: data_base64=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE= 
 
DEBUG: data_base64_fixed=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE=  DEBUG: data_base64_fixed=CiUKEPqlBekzoNEukL7qlsjBCDYSCWhvdHAgZGVtbyABKAEwATgEEAEYASAAKNuv15j6/////wE= 
 
DEBUG: DEBUG:
4. Payload Line 4. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "hotp demo" name: "hotp demo"
algorithm: ALGO_SHA1 algorithm: ALGO_SHA1
@@ -181,12 +175,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -1558849573 batch_id: -1558849573


5. Secret 5. Secret
 
DEBUG: OTP enum type: OTP_HOTP  DEBUG: OTP enum type: OTP_HOTP 
Name: hotp demo Name: hotp demo
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: hotp Type: hotp
@@ -198,17 +192,16 @@ otpauth://hotp/hotp%20demo?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&counter=4
# Name: "encoding: ¿äÄéÉ? (demo)" # Name: "encoding: ¿äÄéÉ? (demo)"
otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D otpauth-migration://offline?data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D
 
DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D', fragment='')  DEBUG: parsed_url=ParseResult(scheme='otpauth-migration', netloc='offline', path='', params='', query='data=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv%2F%2F%2F%2F%2F%2FAQ%3D%3D', fragment='') 
 
DEBUG: querystring params={'data': ['CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==']}  DEBUG: querystring params={'data': ['CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==']} 
 
DEBUG: data_base64=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==  DEBUG: data_base64=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ== 
 
DEBUG: data_base64_fixed=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ==  DEBUG: data_base64_fixed=CjYKEPqlBekzoNEukL7qlsjBCDYSHGVuY29kaW5nOiDCv8Okw4TDqcOJPyAoZGVtbykgASgBMAIQARgBIAAorfCurv//////AQ== 
 
DEBUG: DEBUG:
5. Payload Line 5. Payload Line otp_parameters {
otp_parameters {
secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106" secret: "\372\245\005\3513\240\321.\220\276\352\226\310\301\0106"
name: "encoding: ¿äÄéÉ? (demo)" name: "encoding: ¿äÄéÉ? (demo)"
algorithm: ALGO_SHA1 algorithm: ALGO_SHA1
@@ -218,12 +211,12 @@ otp_parameters {
version: 1 version: 1
batch_size: 1 batch_size: 1
batch_id: -171198419 batch_id: -171198419


6. Secret 6. Secret
 
DEBUG: OTP enum type: OTP_TOTP  DEBUG: OTP enum type: OTP_TOTP 
Name: encoding: ¿äÄéÉ? (demo) Name: encoding: ¿äÄéÉ? (demo)
Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY Secret: 7KSQL2JTUDIS5EF65KLMRQIIGY
Type: totp Type: totp

View File

@@ -1,6 +0,0 @@
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY
otpauth://totp/pi%40raspberrypi?secret=7KSQL2JTUDIS5EF65KLMRQIIGY&issuer=raspberrypi
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

View File

@@ -39,7 +39,7 @@ from utils import (count_files_in_dir, file_exits, read_binary_file_as_stream,
import extract_otp_secrets import extract_otp_secrets
try: try:
import cv2 import cv2 # type: ignore
from extract_otp_secrets import SUCCESS_COLOR, FAILURE_COLOR, FONT, FONT_SCALE, FONT_COLOR, FONT_THICKNESS, FONT_LINE_STYLE from extract_otp_secrets import SUCCESS_COLOR, FAILURE_COLOR, FONT, FONT_SCALE, FONT_COLOR, FONT_THICKNESS, FONT_LINE_STYLE
except ImportError: except ImportError:
# ignore # ignore
@@ -418,37 +418,6 @@ def test_extract_txt_stdout_only_comments(capsys: pytest.CaptureFixture[str]) ->
assert captured.err == '' assert captured.err == ''
def test_extract_urls(capsys: pytest.CaptureFixture[str], tmp_path: pathlib.Path) -> None:
# Arrange
output_file = str(tmp_path / 'test_example_url_list.txt')
# Act
extract_otp_secrets.main(['-q', '-u', output_file, 'example_export.txt'])
# Assert
expected_txt = read_file_to_str('tests/data/url_list_output.txt')
actual_txt = read_file_to_str(output_file)
assert actual_txt == expected_txt
captured = capsys.readouterr()
assert captured.out == ''
assert captured.err == ''
def test_extract_urls_stdout(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-u', '-', 'example_export.txt'])
# Assert
expected_txt = read_file_to_str('tests/data/url_list_output.txt')
captured = capsys.readouterr()
assert captured.out == expected_txt
assert captured.err == ''
def test_extract_not_encoded_plus(capsys: pytest.CaptureFixture[str]) -> None: def test_extract_not_encoded_plus(capsys: pytest.CaptureFixture[str]) -> None:
# Act # Act
extract_otp_secrets.main(['tests/data/test_plus_problem_export.txt']) extract_otp_secrets.main(['tests/data/test_plus_problem_export.txt'])
@@ -559,7 +528,7 @@ def test_normalize_bytes() -> None:
# Generate verbose output: # Generate verbose output:
# for color in '' '-n'; do for level in '' '-v' '-vv' '-vvv'; do python3.11 src/extract_otp_secrets.py example_export.txt $color $level > tests/data/print_verbose_output$color$level.txt; done; done # for color in '' '-n'; do for level in '' '-v' '-vv' '-vvv'; do python3.11 src/extract_otp_secrets.py example_export.txt $color $level > tests/data/print_verbose_output$color$level.txt; done; done
# workaround for PYTHON <= 3.10 # workaround for PYTHON <= 3.10
@pytest.mark.skipif(sys.version_info < (3, 10) or sys.platform.startswith("win"), reason="fileinput.input encoding exists since PYTHON 3.10 OR Windows fatal exception: access violation") @pytest.mark.skipif(sys.version_info < (3, 10), reason="fileinput.input encoding exists since PYTHON 3.10")
@pytest.mark.parametrize("verbose_level", ['', '-v', '-vv', '-vvv']) @pytest.mark.parametrize("verbose_level", ['', '-v', '-vv', '-vvv'])
@pytest.mark.parametrize("color", ['', '-n']) @pytest.mark.parametrize("color", ['', '-n'])
def test_extract_verbose(verbose_level: str, color: str, capsys: pytest.CaptureFixture[str], relaxed: bool) -> None: def test_extract_verbose(verbose_level: str, color: str, capsys: pytest.CaptureFixture[str], relaxed: bool) -> None:
@@ -849,14 +818,7 @@ data=XXXX
Exception: unpack requires a buffer of 4 bytes Exception: unpack requires a buffer of 4 bytes
''' '''
# assert captured.err == first_expected_stderr or captured.err == second_expected_stderr
third_expected_stderr = '''
ERROR: Cannot decode otpauth-migration migration payload.
data=XXXX
Exception: Error parsing message with type 'MigrationPayload'
'''
assert captured.err == first_expected_stderr or captured.err == second_expected_stderr or captured.err == third_expected_stderr
assert captured.out == '' assert captured.out == ''
assert e.value.code == 1 assert e.value.code == 1
assert e.type == SystemExit assert e.type == SystemExit
@@ -869,8 +831,19 @@ def test_wrong_content(capsys: pytest.CaptureFixture[str]) -> None:
# Assert # Assert
captured = capsys.readouterr() captured = capsys.readouterr()
expected_stderr = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
'''
assert captured.out == '' assert captured.out == ''
assert captured.err == EXPECTED_STDERR_OTP_URL_WRONG assert captured.err == expected_stderr
def test_one_wrong_file(capsys: pytest.CaptureFixture[str]) -> None: def test_one_wrong_file(capsys: pytest.CaptureFixture[str]) -> None:
@@ -880,8 +853,19 @@ def test_one_wrong_file(capsys: pytest.CaptureFixture[str]) -> None:
# Assert # Assert
captured = capsys.readouterr() captured = capsys.readouterr()
expected_stderr = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
'''
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured.err == EXPECTED_STDERR_OTP_URL_WRONG assert captured.err == expected_stderr
def test_one_wrong_file_colored(capsys: pytest.CaptureFixture[str]) -> None: def test_one_wrong_file_colored(capsys: pytest.CaptureFixture[str]) -> None:
@@ -891,8 +875,19 @@ def test_one_wrong_file_colored(capsys: pytest.CaptureFixture[str]) -> None:
# Assert # Assert
captured = capsys.readouterr() captured = capsys.readouterr()
expected_stderr = f'''{colorama.Fore.RED}
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given{colorama.Fore.RESET}
{colorama.Fore.RED}
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.{colorama.Fore.RESET}
'''
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT
assert captured.err == EXPECTED_STDERR_COLORED_OTP_URL_WRONG assert captured.err == expected_stderr
def test_one_wrong_line(capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch) -> None: def test_one_wrong_line(capsys: pytest.CaptureFixture[str], monkeypatch: pytest.MonkeyPatch) -> None:
@@ -964,46 +959,6 @@ def test_img_qr_reader_from_file_happy_path(capsys: pytest.CaptureFixture[str])
assert captured.err == '' assert captured.err == ''
@pytest.mark.qreader
def test_img_qr_reader_but_no_otp_from_file(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-n', 'tests/data/qr_but_without_otp.png'])
# Assert
captured = capsys.readouterr()
assert captured.out == ''
assert captured.err == EXPECTED_STDERR_NO_OTP_URL
@pytest.mark.qreader
def test_img_qr_reader_from_wildcard(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-n', 'tests/data/*.png'])
# Assert
captured = capsys.readouterr()
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
assert normalize_testfile_path(captured.err) == EXPECTED_STDERR_NO_OTP_URL
def normalize_testfile_path(text: str) -> str:
return text.replace('tests/data\\', 'tests/data/') if sys.platform.startswith("win") else text
@pytest.mark.qreader
def test_img_qr_reader_from_multiple_files(capsys: pytest.CaptureFixture[str]) -> None:
# Act
extract_otp_secrets.main(['-n', 'tests/data/test_googleauth_export.png', 'tests/data/text_masquerading_as_image.jpeg'])
# Assert
captured = capsys.readouterr()
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG
assert captured.err == EXPECTED_STDERR_BAD_IMAGE
@pytest.mark.qreader @pytest.mark.qreader
def test_img_qr_reader_by_parameter(capsys: pytest.CaptureFixture[str], qr_mode: str) -> None: def test_img_qr_reader_by_parameter(capsys: pytest.CaptureFixture[str], qr_mode: str) -> None:
# Act # Act
@@ -1048,7 +1003,24 @@ def test_img_qr_reader_from_stdin(capsys: pytest.CaptureFixture[str], monkeypatc
# Assert # Assert
captured = capsys.readouterr() captured = capsys.readouterr()
assert captured.out == EXPECTED_STDOUT_FROM_EXAMPLE_EXPORT_PNG expected_stdout = '''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
'''
assert captured.out == expected_stdout
assert captured.err == '' assert captured.err == ''
@@ -1133,9 +1105,19 @@ def test_non_image_file(capsys: pytest.CaptureFixture[str]) -> None:
# Assert # Assert
captured = capsys.readouterr() captured = capsys.readouterr()
expected_stderr = '''
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.
'''
assert captured.err == expected_stderr
assert captured.out == '' assert captured.out == ''
assert captured.err == EXPECTED_STDERR_BAD_IMAGE
def test_next_valid_qr_mode() -> None: def test_next_valid_qr_mode() -> None:
@@ -1189,47 +1171,3 @@ Issuer: Test3
Type: totp Type: totp
''' '''
EXPECTED_STDERR_OTP_URL_WRONG = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
'''
EXPECTED_STDERR_COLORED_OTP_URL_WRONG = f'''{colorama.Fore.RED}
WARN: input is not a otpauth-migration:// url
source: tests/data/test_export_wrong_content.txt
input: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
Maybe a wrong file was given{colorama.Fore.RESET}
{colorama.Fore.RED}
ERROR: could not parse query parameter in input url
source: tests/data/test_export_wrong_content.txt
url: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.{colorama.Fore.RESET}
'''
EXPECTED_STDERR_NO_OTP_URL = '''
WARN: input is not a otpauth-migration:// url
source: tests/data/qr_but_without_otp.png
input: NOT A otpauth-migration:// URL
Maybe a wrong file was given
ERROR: could not parse query parameter in input url
source: tests/data/qr_but_without_otp.png
url: NOT A otpauth-migration:// URL
'''
EXPECTED_STDERR_BAD_IMAGE = '''
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.
'''