Update for modern Python and UV

This commit is contained in:
2025-07-20 16:29:03 +01:00
parent 2b71b60238
commit 977c50e227
6 changed files with 223 additions and 199 deletions

16
Pipfile
View File

@@ -1,16 +0,0 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[dev-packages]
[packages]
rncryptor = "==3.2.0"
bpylist = "==0.1.4"
click = "==6.7"
pyqrcode = "==1.2.1"
pycryptodome = "==3.9.1"
[requires]
python_version = "3.7"

90
Pipfile.lock generated
View File

@@ -1,90 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "3a9aa3cf92e33eb33f1ae837e0b466ec0827564e1abc6766112371ee29d937c3"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"bpylist": {
"hashes": [
"sha256:ac065c9f7b804a201999ebbc31b02df18e0fc29e03bcb1eac3e63696599b88ce"
],
"index": "pypi",
"version": "==0.1.4"
},
"click": {
"hashes": [
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
"sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
],
"index": "pypi",
"version": "==6.7"
},
"pycryptodome": {
"hashes": [
"sha256:0aa49f3fa110f8dc090bad1671a768cc17d3d3bd01566641ffc0d10d0fec8d49",
"sha256:0fafd3c4fb76c6992f34bf2d074f582f388e3b8062b8ba5d65b020634cc221e6",
"sha256:17eb9bd5d30a71b0c8a832e3e9cd2b7723f99907c38dc5dd23e59e8c368a70e2",
"sha256:2776255d5c748782f095ec422d42da2eadd8392ac9de7da23db4aed4231272bd",
"sha256:3500826dc3b9a8fdb762bebe551106081a6bdecd4181a3d1bd0206e48bba8974",
"sha256:3aa0d30326dcdef24c632d5c03b8e4d379c6ae0645082b27dd69ea816bb97ecb",
"sha256:3c7769bdadcc4809508e71997008912cc6d94fd7b5b1f3ef121683ebcac71d81",
"sha256:3e8c97a38dac6dafd180b4696a522b1581dd1a8e0ea60763458be547bac97361",
"sha256:5aca5125a46e458b308b5571ce8fe36d2229f161aa7db27b3ecacded70c6aa8b",
"sha256:62beb75f0688f406946312bfef8923d8ab23f5b8013acded931413625299d317",
"sha256:7725643de3c884a9945a086670787dce637037f32c5c2df7fd602bd5967f3486",
"sha256:872191a02a0c2a3b98dc75c62b32912b220a8ae5ff6ac9e39868f903f55dd6a4",
"sha256:8c501e80960d12328d49e1d409daf426f29364a37c602f257c99509999654650",
"sha256:9512638bfef8ffc94c62751965a4733c3792104dc84771ba54ce0f80f49134df",
"sha256:962043051afa7a5ab071b0d8996dc00e564327a18566d3e574a39cb6e097b462",
"sha256:9db72b18b30902a83fa57b0d7dae4ce24f85186695e3bea0d423f1ec7c5b3fbe",
"sha256:9ffd4f0bfb5949dfa0e5cedef836364f18da0deb2fba04671607fb3b59b29112",
"sha256:a26819f693cf5fc0a2373a3e4b91c38e359cad9f00020a885b667c77f28738d5",
"sha256:a3efc575a53511c48361d933e12e07c2eb940db1afda0995285176c372ab7352",
"sha256:ababd6685b9d94729a851a0615482156afdacbeaabeea60f67961db0e975b1af",
"sha256:b0e9c8c270cd3f8c73b53139f0708f257189a00bbc898be6d3f03995e5f7edc2",
"sha256:b74173b13c221ee96b608212b9adc2c459a73d3632f04490df42e4f07e7041e6",
"sha256:bed297f75ba19cefe2d10beb4959f4f8cb62c2560a3998ad87479485098ee939",
"sha256:c639f09e8ce8ad5af9884233f952ade4b73a11b7d41d3b9bb7d4e64d9e1df164",
"sha256:c7bc308be67288af1cd44668d59e36356f0ce518337899079ddb0235bd55db79",
"sha256:cca152dcebc318833ba70499190ce17ee81b525404e2a7548c77f52b439306a7",
"sha256:d5261d22bc3a54db26f11dabcda14bbaab72080977e083d795b4b1d1b510c774",
"sha256:d81111e3da7fc9eee825ba7d8a68b3c1464f41110ef98a7280e0c7fb82c91e73",
"sha256:d95fafa899abb9f82e55ff43f423e100784312b43932514f2c05d41cbb20323e",
"sha256:de411a64d4105d4424441833bd25943208e58c846abf981bba5bbeeba88a49c3",
"sha256:e02c7b3d05b88ff1a236e49a252b2bf8444d3a1d04a056784af766c0909eba36",
"sha256:fbafe9b01b717e0bfbc83cd740ff5bf5cdd3f208815be470ea203942b899bbdf"
],
"index": "pypi",
"version": "==3.9.1"
},
"pyqrcode": {
"hashes": [
"sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6",
"sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"
],
"index": "pypi",
"version": "==1.2.1"
},
"rncryptor": {
"hashes": [
"sha256:156253246f3e3521e5080191e9b4ec100e162d07c261a9df86169f8943bcc7a3",
"sha256:a3b521d22953bffc4f125faf53f4c9c2a41c8186e0b0e331314abe455be92c48"
],
"index": "pypi",
"version": "==3.2.0"
}
},
"develop": {}
}

View File

@@ -4,10 +4,12 @@ This tool allows for decrypting the encrypted backups/account files created by [
If you find problems with the file format (in particular security related issues), do not hesitate and file an issue.
This is a fork of the original repository at [CooperRS/decrypt-otpauth-files](https://github.com/CooperRS/decrypt-otpauth-files) with a aim to get the script working out of the box with modern Python versions.
## Requirements
- [Python 3.7](https://www.python.org/downloads/)
- [pipenv](https://github.com/pypa/pipenv)
- [Python 3.12](https://www.python.org/downloads/)
- [uv](https://astral.sh/uv)
- An encrypted OTP Auth backup/account file
## Usage
@@ -21,20 +23,20 @@ cd decrypt-otpauth-files
2. Install dependencies
```
pipenv install
```shell-session
$ uv sync
```
3. Decrypt your OTP Auth file
```
```shell-session
# Decrypt a full backup file
pipenv run python decrypt_otpauth.py decrypt_backup --encrypted-otpauth-backup <path to your OTP Auth backup>
$ uv run python decrypt_otpauth.py decrypt_backup --encrypted-otpauth-backup <path to your OTP Auth backup>
```
```
```shell-session
# Decrypt a single account export
pipenv run python decrypt_otpauth.py decrypt_account --encrypted-otpauth-account <path to your OTP Auth account>
$ uv run python decrypt_otpauth.py decrypt_account --encrypted-otpauth-account <path to your OTP Auth account>
```
## Demo

182
decrypt_otpauth.py Normal file → Executable file
View File

@@ -1,20 +1,16 @@
#!/usr/bin/env python3
import base64
import click
import getpass
import hashlib
import plistlib
from enum import Enum
from urllib.parse import quote
import click
import pyqrcode
from bpylist import archiver
from bpylist.archive_types import uid
from bpylist2 import archiver
from Crypto.Cipher import AES
from rncryptor import RNCryptor
from rncryptor import bord
from rncryptor import RNCryptor, bord
class Type(Enum):
@@ -25,11 +21,11 @@ class Type(Enum):
@property
def uri_value(self):
if self.value == 0:
return 'unknown'
return "unknown"
if self.value == 1:
return 'hotp'
return "hotp"
if self.value == 2:
return 'totp'
return "totp"
class Algorithm(Enum):
@@ -42,27 +38,25 @@ class Algorithm(Enum):
@property
def uri_value(self):
if self.value == 0:
return 'sha1'
return "sha1"
if self.value == 1:
return 'sha1'
return "sha1"
if self.value == 2:
return 'sha256'
return "sha256"
if self.value == 3:
return 'sha512'
return "sha512"
if self.value == 4:
return 'md5'
return "md5"
class MutableString:
def decode_archive(archive):
return archive.decode('NS.string')
return archive.decode("NS.string")
class MutableData:
def decode_archive(archive):
return bytes(archive.decode('NS.data'))
return bytes(archive.decode("NS.data"))
class OTPFolder:
@@ -74,11 +68,11 @@ class OTPFolder:
self.accounts = accounts
def __repr__(self):
return f'<OTPFolder: {self.name}>'
return f"<OTPFolder: {self.name}>"
def decode_archive(archive):
name = archive.decode('name')
accounts = archive.decode('accounts')
name = archive.decode("name")
accounts = archive.decode("accounts")
return OTPFolder(name, accounts)
@@ -93,7 +87,9 @@ class OTPAccount:
period = None
refDate = None
def __init__(self, label, issuer, secret, type, algorithm, digits, counter, period, refDate):
def __init__(
self, label, issuer, secret, type, algorithm, digits, counter, period, refDate
):
self.label = label
self.issuer = issuer
self.secret = secret
@@ -105,7 +101,7 @@ class OTPAccount:
self.refDate = refDate
def __repr__(self):
return f'<OTPAccount: {self.issuer} ({self.label})>'
return f"<OTPAccount: {self.issuer} ({self.label})>"
def decode_archive(archive):
label = archive.decode("label")
@@ -117,7 +113,9 @@ class OTPAccount:
counter = archive.decode("counter")
period = archive.decode("period")
refDate = archive.decode("refDate")
return OTPAccount(label, issuer, secret, type, algorithm, digits, counter, period, refDate)
return OTPAccount(
label, issuer, secret, type, algorithm, digits, counter, period, refDate
)
def from_dict(in_dict):
label = in_dict.get("label")
@@ -129,40 +127,42 @@ class OTPAccount:
counter = in_dict.get("counter")
period = in_dict.get("period")
refDate = in_dict.get("refDate")
return OTPAccount(label, issuer, secret, type, algorithm, digits, counter, period, refDate)
return OTPAccount(
label, issuer, secret, type, algorithm, digits, counter, period, refDate
)
def otp_uri(self):
otp_type = self.type.uri_value
otp_label = quote(f'{self.issuer}:{self.label}')
otp_label = quote(f"{self.issuer}:{self.label}")
otp_parameters = {
'secret': base64.b32encode(self.secret).decode("utf-8").rstrip("="),
'algorithm': self.algorithm.uri_value,
'period': self.period,
'digits': self.digits,
'issuer': self.issuer,
'counter': self.counter,
"secret": base64.b32encode(self.secret).decode("utf-8").rstrip("="),
"algorithm": self.algorithm.uri_value,
"period": self.period,
"digits": self.digits,
"issuer": self.issuer,
"counter": self.counter,
}
otp_parameters = '&'.join([f'{str(k)}={quote(str(v))}' for (k, v) in otp_parameters.items() if v])
return f'otpauth://{otp_type}/{otp_label}?{otp_parameters}'
otp_parameters = "&".join(
[f"{str(k)}={quote(str(v))}" for (k, v) in otp_parameters.items() if v]
)
return f"otpauth://{otp_type}/{otp_label}?{otp_parameters}"
archiver.update_class_map({'NSMutableData': MutableData})
archiver.update_class_map({'NSMutableString': MutableString})
archiver.update_class_map({'ACOTPFolder': OTPFolder})
archiver.update_class_map({'ACOTPAccount': OTPAccount})
archiver.update_class_map({"NSMutableData": MutableData})
archiver.update_class_map({"NSMutableString": MutableString})
archiver.update_class_map({"ACOTPFolder": OTPFolder})
archiver.update_class_map({"ACOTPAccount": OTPAccount})
class RawRNCryptor(RNCryptor):
def post_decrypt_data(self, data):
"""Remove useless symbols which
appear over padding for AES (PKCS#7)."""
data = data[:-bord(data[-1])]
appear over padding for AES (PKCS#7)."""
data = data[: -bord(data[-1])]
return data
class DangerousUnarchive(archiver.Unarchive):
def decode_object(self, index):
if index == 0:
return None
@@ -178,8 +178,8 @@ class DangerousUnarchive(archiver.Unarchive):
if not isinstance(raw_obj, dict):
return raw_obj
class_uid = raw_obj.get('$class')
if not isinstance(class_uid, uid):
class_uid = raw_obj.get("$class")
if not isinstance(class_uid, plistlib.UID):
raise archiver.MissingClassUID(raw_obj)
klass = self.class_for_uid(class_uid)
@@ -192,7 +192,7 @@ class DangerousUnarchive(archiver.Unarchive):
def render_qr_to_terminal(otp_uri, type, issuer, label):
qr = pyqrcode.create(otp_uri, error="L")
click.echo("")
click.echo(f'{type}: {issuer} - {label}')
click.echo(f"{type}: {issuer} - {label}")
click.echo(qr.terminal(quiet_zone=4))
click.echo("")
@@ -203,45 +203,51 @@ def cli():
@cli.command()
@click.option('--encrypted-otpauth-account',
help="path to your encrypted OTP Auth account (.otpauth)",
required=True,
type=click.File('rb'))
@click.option(
"--encrypted-otpauth-account",
help="path to your encrypted OTP Auth account (.otpauth)",
required=True,
type=click.File("rb"),
)
def decrypt_account(encrypted_otpauth_account):
# Get password from user
password = getpass.getpass(f'Password for export file {encrypted_otpauth_account.name}: ')
password = getpass.getpass(
f"Password for export file {encrypted_otpauth_account.name}: "
)
# Get IV and key for wrapping archive
iv = bytes(16)
key = hashlib.sha256('OTPAuth'.encode('utf-8')).digest()
key = hashlib.sha256("OTPAuth".encode("utf-8")).digest()
# Decrypt wrapping archive
data = AES.new(key, AES.MODE_CBC, iv).decrypt(encrypted_otpauth_account.read())
data = data[:-data[-1]]
data = data[: -data[-1]]
# Decode wrapping archive
archive = archiver.Unarchive(data).top_object()
if archive['Version'] == 1.1:
if archive["Version"] == 1.1:
account = decrypt_account_11(archive, password)
elif archive['Version'] == 1.2:
elif archive["Version"] == 1.2:
account = decrypt_account_12(archive, password)
else:
click.echo(f'Encountered unknow file version: {archive["Version"]}')
click.echo(f"Encountered unknow file version: {archive['Version']}")
return
render_qr_to_terminal(account.otp_uri(), account.type, account.issuer, account.label)
render_qr_to_terminal(
account.otp_uri(), account.type, account.issuer, account.label
)
def decrypt_account_11(archive, password):
# Get IV and key for actual archive
iv = hashlib.sha1(archive['IV']).digest()[:16]
salt = archive['Salt']
key = hashlib.sha256((salt + '-' + password).encode('utf-8')).digest()
iv = hashlib.sha1(archive["IV"]).digest()[:16]
salt = archive["Salt"]
key = hashlib.sha256((salt + "-" + password).encode("utf-8")).digest()
# Decrypt actual archive
data = AES.new(key, AES.MODE_CBC, iv).decrypt(archive['Data'])
data = data[:-data[-1]]
data = AES.new(key, AES.MODE_CBC, iv).decrypt(archive["Data"])
data = data[: -data[-1]]
# Decode actual archive
archive = DangerousUnarchive(data).top_object()
@@ -252,7 +258,7 @@ def decrypt_account_11(archive, password):
def decrypt_account_12(archive, password):
# Decrypt using RNCryptor
data = data = RawRNCryptor().decrypt(archive['Data'], password)
data = data = RawRNCryptor().decrypt(archive["Data"], password)
# Decode archive
archive = DangerousUnarchive(data).top_object()
@@ -262,63 +268,69 @@ def decrypt_account_12(archive, password):
@cli.command()
@click.option('--encrypted-otpauth-backup',
help="path to your encrypted OTP Auth backup (.otpauthdb)",
required=True,
type=click.File('rb'))
@click.option(
"--encrypted-otpauth-backup",
help="path to your encrypted OTP Auth backup (.otpauthdb)",
required=True,
type=click.File("rb"),
)
def decrypt_backup(encrypted_otpauth_backup):
# Get password from user
password = getpass.getpass(f'Password for export file {encrypted_otpauth_backup.name}: ')
password = getpass.getpass(
f"Password for export file {encrypted_otpauth_backup.name}: "
)
# Get IV and key for wrapping archive
iv = bytes(16)
key = hashlib.sha256('Authenticator'.encode('utf-8')).digest()
key = hashlib.sha256("Authenticator".encode("utf-8")).digest()
# Decrypt wrapping archive
data = AES.new(key, AES.MODE_CBC, iv).decrypt(encrypted_otpauth_backup.read())
data = data[:-data[-1]]
data = data[: -data[-1]]
# Decode wrapping archive
archive = archiver.Unarchive(data).top_object()
if archive['Version'] == 1.0:
if archive["Version"] == 1.0:
accounts = decrypt_backup_10(archive, password)
elif archive['Version'] == 1.1:
elif archive["Version"] == 1.1:
accounts = decrypt_backup_11(archive, password)
else:
click.echo(f'Encountered unknow file version: {archive["Version"]}')
click.echo(f"Encountered unknow file version: {archive['Version']}")
return
for account in accounts:
render_qr_to_terminal(account.otp_uri(), account.type, account.issuer, account.label)
render_qr_to_terminal(
account.otp_uri(), account.type, account.issuer, account.label
)
input("Press Enter to continue...")
def decrypt_backup_10(archive, password):
# Get IV and key for actual archive
iv = hashlib.sha1(archive['IV'].encode('utf-8')).digest()[:16]
salt = archive['Salt']
key = hashlib.sha256((salt + '-' + password).encode('utf-8')).digest()
iv = hashlib.sha1(archive["IV"].encode("utf-8")).digest()[:16]
salt = archive["Salt"]
key = hashlib.sha256((salt + "-" + password).encode("utf-8")).digest()
# Decrypt actual archive
data = AES.new(key, AES.MODE_CBC, iv).decrypt(archive['WrappedData'])
data = data[:-data[-1]]
data = AES.new(key, AES.MODE_CBC, iv).decrypt(archive["WrappedData"])
data = data[: -data[-1]]
# Decode actual archive
archive = DangerousUnarchive(data).top_object()
return [account for folder in archive['Folders'] for account in folder.accounts]
return [account for folder in archive["Folders"] for account in folder.accounts]
def decrypt_backup_11(archive, password):
# Decrypt using RNCryptor
data = data = RawRNCryptor().decrypt(archive['WrappedData'], password)
data = data = RawRNCryptor().decrypt(archive["WrappedData"], password)
# Decode archive
archive = DangerousUnarchive(data).top_object()
return [account for folder in archive['Folders'] for account in folder.accounts]
return [account for folder in archive["Folders"] for account in folder.accounts]
if __name__ == '__main__':
if __name__ == "__main__":
cli()

14
pyproject.toml Normal file
View File

@@ -0,0 +1,14 @@
[project]
name = "decrypt-otpauth-files"
version = "1.0.0"
description = "A tool for decrypting the encrypted backups/account files created by OTP Auth for iOS"
license = { file = "LICENSE" }
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"bpylist2>=4.1.1",
"click>=8.2.1",
"pycryptodome>=3.23.0",
"pyqrcode>=1.2.1",
"rncryptor>=3.3.0",
]

102
uv.lock generated Normal file
View File

@@ -0,0 +1,102 @@
version = 1
revision = 2
requires-python = ">=3.12"
[[package]]
name = "bpylist2"
version = "4.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/34/eb90ff6be953f6e4df08d4e8c0b761bea144242b6d711e922113411cc631/bpylist2-4.1.1.tar.gz", hash = "sha256:0cc63284aee42f5c7e0ec87f8f59cdd35aaed05ad12d866b1868ea0c0caaafe1", size = 18000, upload-time = "2023-09-11T16:58:00.758Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/e5/b552c94fc965153c9dcad4b64202e5170d4918716bc082d4fa6c6230579c/bpylist2-4.1.1-py3-none-any.whl", hash = "sha256:4862eab78d9d908d532393208b6771cebc8debef99ab851b54a0a0e28e2bec6b", size = 16596, upload-time = "2023-09-11T16:57:59.526Z" },
]
[[package]]
name = "click"
version = "8.2.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342, upload-time = "2025-05-20T23:19:49.832Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215, upload-time = "2025-05-20T23:19:47.796Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "decrypt-otpauth-files"
version = "1.0.0"
source = { virtual = "." }
dependencies = [
{ name = "bpylist2" },
{ name = "click" },
{ name = "pycryptodome" },
{ name = "pyqrcode" },
{ name = "rncryptor" },
]
[package.metadata]
requires-dist = [
{ name = "bpylist2", specifier = ">=4.1.1" },
{ name = "click", specifier = ">=8.2.1" },
{ name = "pycryptodome", specifier = ">=3.23.0" },
{ name = "pyqrcode", specifier = ">=1.2.1" },
{ name = "rncryptor", specifier = ">=3.3.0" },
]
[[package]]
name = "pycryptodome"
version = "3.23.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" },
{ url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" },
{ url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" },
{ url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" },
{ url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" },
{ url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" },
{ url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" },
{ url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" },
{ url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" },
{ url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" },
{ url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" },
{ url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" },
{ url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" },
{ url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" },
{ url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" },
{ url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" },
{ url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" },
{ url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" },
{ url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" },
{ url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" },
{ url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" },
{ url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" },
]
[[package]]
name = "pyqrcode"
version = "1.2.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/37/61/f07226075c347897937d4086ef8e55f0a62ae535e28069884ac68d979316/PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5", size = 36989, upload-time = "2016-06-20T03:28:03.411Z" }
[[package]]
name = "rncryptor"
version = "3.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycryptodome" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0e/f0/a6b66f0a13bf622231613023fd4682a6abf480fde48c058c7b218d9b635a/rncryptor-3.3.0.tar.gz", hash = "sha256:efdd61d56627e645dcbb1b9cfe22806decf676c0089b6f993f280de82d66e70e", size = 4129, upload-time = "2019-08-14T16:49:09.398Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/03/57/0734b54bd4a48df3dbd3aa0ac86fd24296bc22e3e9aa90961eab39fb223d/rncryptor-3.3.0-py2.py3-none-any.whl", hash = "sha256:ba932299aee25524537a32112cba8787965f8de6e46550684b71a3c1255b0e62", size = 4264, upload-time = "2019-08-14T16:49:07.864Z" },
]