{"id":47792575,"url":"https://github.com/rmlibre/aiootp","last_synced_at":"2026-04-03T15:51:28.780Z","repository":{"id":62559782,"uuid":"260309740","full_name":"rmlibre/aiootp","owner":"rmlibre","description":"a high-level async cryptographic anonymity library to scale, simplify, \u0026 automate privacy best practices for secure data \u0026 identity processing, communication, \u0026 storage.","archived":false,"fork":false,"pushed_at":"2025-11-18T20:26:14.000Z","size":4031,"stargazers_count":13,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-27T06:52:23.208Z","etag":null,"topics":["aead","aead-commitment","asynchronous","canonicalization","context-committing","cryptography","cryptography-library","databases","developer-tools","domain-separation","ed25519","keccak","key-committing","key-derivation","mnemonic-generator","nonce-reuse-misuse-resistant","online","sha3","tweakable","x25519"],"latest_commit_sha":null,"homepage":"https://twitter.com/aiootp","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rmlibre.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGES.rst","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-04-30T20:18:33.000Z","updated_at":"2025-11-18T20:26:19.000Z","dependencies_parsed_at":"2024-06-26T23:06:02.818Z","dependency_job_id":"20e6c9dd-92fc-42ea-95a5-cda7877a4505","html_url":"https://github.com/rmlibre/aiootp","commit_stats":{"total_commits":562,"total_committers":1,"mean_commits":562.0,"dds":0.0,"last_synced_commit":"5aeafab87a92c1b4b8ed549e721cb8a43442e44a"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/rmlibre/aiootp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmlibre%2Faiootp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmlibre%2Faiootp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmlibre%2Faiootp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmlibre%2Faiootp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rmlibre","download_url":"https://codeload.github.com/rmlibre/aiootp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rmlibre%2Faiootp/sbom","scorecard":{"id":779052,"data":{"date":"2025-08-11","repo":{"name":"github.com/rmlibre/aiootp","commit":"e8e4338ad099e726720b9ced4b60202671176fe5"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/linux-pypi-release-tests.yml:1","Warn: no topLevel permission defined: .github/workflows/linux-pypi-release.yml:1","Warn: no topLevel permission defined: .github/workflows/linux-python-app.yml:1","Warn: no topLevel permission defined: .github/workflows/macos-pypi-release-tests.yml:1","Warn: no topLevel permission defined: .github/workflows/macos-pypi-release.yml:1","Warn: no topLevel permission defined: .github/workflows/macos-python-app.yml:1","Warn: no topLevel permission defined: .github/workflows/windows-pypi-release-tests.yml:1","Warn: no topLevel permission defined: .github/workflows/windows-pypi-release.yml:1","Warn: no topLevel permission defined: .github/workflows/windows-python-app.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linux-pypi-release-tests.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/linux-pypi-release-tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linux-pypi-release-tests.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/linux-pypi-release-tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linux-pypi-release.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/linux-pypi-release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linux-pypi-release.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/linux-pypi-release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linux-python-app.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/linux-python-app.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/linux-python-app.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/linux-python-app.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/macos-pypi-release-tests.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/macos-pypi-release-tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/macos-pypi-release-tests.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/macos-pypi-release-tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/macos-pypi-release.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/macos-pypi-release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/macos-pypi-release.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/macos-pypi-release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/macos-python-app.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/macos-python-app.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/macos-python-app.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/macos-python-app.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows-pypi-release-tests.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/windows-pypi-release-tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows-pypi-release-tests.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/windows-pypi-release-tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows-pypi-release.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/windows-pypi-release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows-pypi-release.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/windows-pypi-release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows-python-app.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/windows-python-app.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/windows-python-app.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/rmlibre/aiootp/windows-python-app.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/linux-pypi-release-tests.yml:23","Warn: pipCommand not pinned by hash: .github/workflows/linux-pypi-release.yml:23","Warn: pipCommand not pinned by hash: .github/workflows/linux-python-app.yml:22","Warn: pipCommand not pinned by hash: .github/workflows/macos-pypi-release-tests.yml:23","Warn: pipCommand not pinned by hash: .github/workflows/macos-pypi-release.yml:23","Warn: pipCommand not pinned by hash: .github/workflows/macos-python-app.yml:22","Info:   0 out of  18 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   6 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.23.16 not signed: https://api.github.com/repos/rmlibre/aiootp/releases/204872998","Warn: release artifact v0.23.15 not signed: https://api.github.com/repos/rmlibre/aiootp/releases/179184634","Warn: release artifact v0.23.14 not signed: https://api.github.com/repos/rmlibre/aiootp/releases/169844952","Warn: release artifact v0.23.13 not signed: https://api.github.com/repos/rmlibre/aiootp/releases/169683280","Warn: release artifact v0.23.12 not signed: https://api.github.com/repos/rmlibre/aiootp/releases/169661704","Warn: release artifact v0.23.16 does not have provenance: https://api.github.com/repos/rmlibre/aiootp/releases/204872998","Warn: release artifact v0.23.15 does not have provenance: https://api.github.com/repos/rmlibre/aiootp/releases/179184634","Warn: release artifact v0.23.14 does not have provenance: https://api.github.com/repos/rmlibre/aiootp/releases/169844952","Warn: release artifact v0.23.13 does not have provenance: https://api.github.com/repos/rmlibre/aiootp/releases/169683280","Warn: release artifact v0.23.12 does not have provenance: https://api.github.com/repos/rmlibre/aiootp/releases/169661704"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-23T04:26:07.026Z","repository_id":62559782,"created_at":"2025-08-23T04:26:07.026Z","updated_at":"2025-08-23T04:26:07.026Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31361239,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T15:19:21.178Z","status":"ssl_error","status_checked_at":"2026-04-03T15:19:20.670Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aead","aead-commitment","asynchronous","canonicalization","context-committing","cryptography","cryptography-library","databases","developer-tools","domain-separation","ed25519","keccak","key-committing","key-derivation","mnemonic-generator","nonce-reuse-misuse-resistant","online","sha3","tweakable","x25519"],"created_at":"2026-04-03T15:51:26.849Z","updated_at":"2026-04-03T15:51:28.482Z","avatar_url":"https://github.com/rmlibre.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n.. image:: https://raw.githubusercontent.com/rmlibre/aiootp/main/logo.png\n    :target: https://raw.githubusercontent.com/rmlibre/aiootp/main/logo.png\n    :alt: aiootp python package logo\n\n\n\n\naiootp — an async privacy, anonymity, \u0026 cryptography library.\n==============================================================\n\n``aiootp`` is a high-level async cryptographic anonymity library to scale, simplify, \u0026 automate privacy best practices for secure data \u0026 identity processing, communication, \u0026 storage.\n\nIt's home to a family of novel online, salt misuse-reuse resistant, tweakable, \u0026 fully context committing AEAD ciphers. A 256-byte block-wise stream cipher named ``Chunky2048``. And, a robust 32-byte hybrid block-cipher / stream-cipher called ``Slick256``. The design goals behind this family of ciphers are to be simple \u0026 efficient, while achieving modern security notions, wide security margins, \u0026 aiming towards incorporating information theoretic undecidability guarantees where possible.\n\nWe hope to give users \u0026 applications empowering developer-friendly privacy enhancing tools with strong, biased defaults. In so doing, increase the overall security, privacy, \u0026 anonymity in the digital world. Users will find ``aiootp`` to be easy to write, easy to read, \u0026 fun.\n\n\n\n\n.. image:: https://img.shields.io/pypi/v/aiootp?style=flat-square\u0026color=darkred\u0026logo=pypi\u0026logoColor=3776AB\n    :target: https://pypi.org/project/aiootp/\n    :alt: aiootp PyPI Package Version\n\n.. image:: https://img.shields.io/pypi/wheel/aiootp?style=flat-square\u0026color=darkorange\u0026logo=pypi\u0026logoColor=gold\n    :target: https://www.piwheels.org/project/aiootp/\n    :alt: Python Wheel Availability Badge\n\n.. image:: https://img.shields.io/pypi/pyversions/aiootp?style=flat-square\u0026color=gold\u0026logo=python\u0026logoColor=3776AB\n    :target: https://github.com/rmlibre/aiootp/actions\n    :alt: Python Versions Badge\n\n.. image:: https://img.shields.io/badge/Linter-Ruff-D7FF64?style=flat-square\u0026logo=ruff\n    :target: https://github.com/astral-sh/ruff\n    :alt: Ruff Linter Badge\n\n.. image:: https://img.shields.io/badge/Formatter-Ruff-D7FF64?style=flat-square\u0026logo=ruff\n   :target: https://github.com/astral-sh/ruff\n   :alt: Ruff Formatter Badge\n\n.. image:: https://img.shields.io/github/actions/workflow/status/rmlibre/aiootp/linux-python-app.yml?style=flat-square\u0026color=chartreuse\u0026logo=ubuntu\u0026logoColor=#E95420\n    :target: https://github.com/rmlibre/aiootp/actions/workflows/linux-python-app.yml\n    :alt: Linux Build Workflow Status\n\n.. image:: https://img.shields.io/github/actions/workflow/status/rmlibre/aiootp/windows-python-app.yml?style=flat-square\u0026color=chartreuse\u0026logo=gitforwindows\u0026logoColor=00A4EF\n    :target: https://github.com/rmlibre/aiootp/actions/workflows/windows-python-app.yml\n    :alt: Windows Build Workflow Status\n\n.. image:: https://img.shields.io/github/actions/workflow/status/rmlibre/aiootp/macos-python-app.yml?style=flat-square\u0026color=chartreuse\u0026logo=apple\u0026logoColor=black\n    :target: https://github.com/rmlibre/aiootp/actions/workflows/macos-python-app.yml\n    :alt: MacOS Build Workflow Status\n\n.. image:: https://img.shields.io/badge/coverage-99%25-1e4ede?style=flat-square\u0026logo=codecov\u0026logoColor=maroon\n    :target: https://github.com/rmlibre/aiootp/actions\n    :alt: Test Coverage Badge\n\n.. image:: https://img.shields.io/badge/License-AGPL%20v3-purple?style=flat-square\u0026logo=GNU\u0026logoColor=white\n    :target: https://github.com/rmlibre/aiootp/blob/main/LICENSE\n    :alt: Gnu Affero General Public License Badge\n\n\n\n\nQuick Install\n-------------\n\n.. code-block:: shell\n\n    sudo apt-get install python3-pip\n\n    pip3 install --user --upgrade aiootp\n\n\n\n\nDevelopment \u0026 Testing\n---------------------\n\n.. code-block:: shell\n\n    # Setup environment\n\n    sudo apt-get install pipx\n\n    pipx install uv\n\n    mkdir aiootp \u0026\u0026 cd aiootp\n\n    git clone https://github.com/rmlibre/aiootp.git \u0026\u0026 cd aiootp\n\n    uv sync --extra dev \u0026\u0026 source .venv/bin/activate\n\n\n    # Run formatter, linter, \u0026 tests on new changes\n\n    ruff format . \u0026\u0026 ruff check --fix .\n\n    coverage run -m pytest tests/all_aiootp_tests.py\n\n    coverage combine \u0026\u0026 coverage report \u0026\u0026 coverage html\n\n\n    # Tests can also be run across specific Python versions\n\n    uv python install 3.9 3.10 3.11 3.12 3.13 3.14\n\n    uv run --python 3.14 --extra dev coverage run -m pytest tests/all_aiootp_tests.py\n\n\n\n\n.. warning::\n\n    ``aiootp`` is **experimental software** that works with Python 3.9+. It's a work in progress. Its algorithms \u0026 programming API are likely to change with future updates, \u0026 it isn't bug free.\n\n    ``aiootp`` provides security tools \u0026 misc utilities that're designed to be developer-friendly \u0026 privacy preserving.\n\n    As a security tool, ``aiootp`` needs to be tested \u0026 improved extensively by the programming \u0026 cryptography communities to ensure its implementations are sound. We provide no guarantees.\n\n    This software hasn't yet been audited by 3rd-party security professionals.\n\n\n\n\n_`Table Of Contents`\n--------------------\n\n- `Transparently Encrypted Databases`_\n\n  a) `Ideal Initialization`_\n\n  b) `User Profiles`_\n\n  c) `Tags`_\n\n  d) `Metatags`_\n\n  e) `Basic Management`_\n\n  g) `Encrypt / Decrypt`_\n\n\n- `Chunky2048 \u0026 Slick256 Ciphers`_\n\n  a) `High-level Functions`_\n\n  b) `High-level Generators`_\n\n  c) `Chunky2048 Algorithm`_\n\n  d) `Slick256 Algorithm`_\n\n\n- `Passcrypt`_\n\n  a) `Hashing \u0026 Verifying Passphrases`_\n\n  b) `Passcrypt Algorithm Overview`_\n\n\n- `X25519 \u0026 Ed25519`_\n\n  a) `X25519`_\n\n  b) `Ed25519`_\n\n\n\n\n_`Transparently Encrypted Databases` .............. `Table Of Contents`_\n------------------------------------------------------------------------\n\nThe package's ``AsyncDatabase`` \u0026 ``Database`` classes are very powerful data persistence utilities. They're key-value type databases, \u0026 they automatically handle encryption \u0026 decryption of user data \u0026 metadata, providing a Pythonic interface for storing \u0026 retrieving any bytes or JSON serializable objects. They're designed to seamlessly bring encrypted bytes at rest to users as dynamic objects in use.\n\n\n_`Ideal Initialization` ........................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nMake a new user key with a fast, cryptographically secure pseudo-random number generator. Then this strong 64-byte key can be used to create a database object.\n\n.. code-block:: python\n\n    from aiootp import acsprng, AsyncDatabase\n\n\n    key = await acsprng()\n\n    db = await AsyncDatabase(key)\n\n\n_`User Profiles` .................................. `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWith User Profiles, passphrases may be used instead to open a database. Often, passwords \u0026 passphrases contain very little entropy. So, they aren't recommended for that reason. However, profiles provide a succinct way to use passphrases more safely. They do this by deriving strong keys from low entropy user input using the memory/cpu hard passcrypt algorithm, \u0026 a secret salt which is automatically generated \u0026 stored on the user's filesystem.\n\n.. code-block:: python\n\n    db = await AsyncDatabase.agenerate_profile(\n\n        b\"server-url.com\",     # Here an unlimited number of bytes-type\n                               # arguments can be passed as additional\n        b\"address@email.net\",  # optional credentials.\n\n        username=b\"username\",\n\n        passphrase=b\"passphrase\",\n\n        salt=b\"optional salt keyword argument\",\n                  # Optional passcrypt configuration:\n        mb=256,   # The memory cost in Mebibytes (MiB)\n\n        cpu=2,    # The computational complexity \u0026 number of iterations\n\n        cores=8,  # How many parallel processes passcrypt will utilize\n\n    )\n\n\n_`Tags` ........................................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nData within databases are values that are primarily organized by Tag keys. Tags are simply string labels, and the data stored under them can be any bytes or JSON serializable objects.\n\nUsing bracketed assignment adds tags to the cache. Changes in the cache are saved to disk when the database context closes.\n\n.. code-block:: python\n\n    async with db:\n\n        db[\"tag\"] = {\"any\": [\"JSON\", \"serializable\", \"object\"]}\n\n        db[\"8b362accfdf600ea\"] = b\"some amount of data.\"\n\n\nAll instance tags are viewable. Each tag has its data saved to a separate, independent file, which is quite convenient when working in asynchronous, concurrent, \u0026 distributed settings.\n\n.. code-block:: python\n\n    db.tags\n    \u003e\u003e\u003e {'tag', '8b362accfdf600ea'}\n\n    db.filenames\n    \u003e\u003e\u003e {'0z0l10btu_yd-n4quc8tsj9baqu8xmrxz87ix',\n     '197ulmqmxg15lebm26zaahpqnabwr8sprojuh'}\n\n\nLearning how to manage tags stored in the cache vs. saved to disk is essential.\n\n.. code-block:: python\n\n    # stores data in the cache -\u003e\n\n    await db.aset_tag(\"new_tag\", [\"data\", \"goes\", \"here\"])\n\n\n    # reads from disk if not in the cache -\u003e\n\n    await db.aquery_tag(\"new_tag\")\n    \u003e\u003e\u003e ['data', 'goes', 'here']\n\n\n    # saved in the cache, still not to disk -\u003e\n\n    tag_path = db.path / await db.afilename(\"new_tag\")\n\n    assert \"new_tag\" in db\n\n    assert not tag_path.is_file()\n\n\n    # now it gets saved to disk -\u003e\n\n    await db.asave_tag(\"new_tag\")\n\n    assert tag_path.is_file()\n\n\nUnsaved changes in the cache can be rolled back, \u0026 data saved to disk can be popped from the database.\n\n.. code-block:: python\n\n    db[\"new_tag\"].append(\"!\")\n\n    db[\"new_tag\"]\n    \u003e\u003e\u003e ['data', 'goes', 'here', '!']\n\n    await db.arollback_tag(\"new_tag\")\n\n    db[\"new_tag\"]\n    \u003e\u003e\u003e ['data', 'goes', 'here']\n\n    await db.apop_tag(\"new_tag\")\n    \u003e\u003e\u003e ['data', 'goes', 'here']\n\n    \"new_tag\" in db\n    \u003e\u003e\u003e False\n\n    tag_path.is_file()\n    \u003e\u003e\u003e False\n\n    db[\"new_tag\"]\n    \u003e\u003e\u003e\n\n\n    #\n\nAccess to data is open to the user, so care must be taken not to let external API calls touch the database without accounting for how that can go wrong.\n\n\n_`Metatags` ....................................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nMetatags are used to organize data by string names \u0026 domain-separated cryptographic material. They're fully-fledged databases all on their own, with their own distinct key material too. They're accessible from the parent through an attribute that's added to the parent instance with the same name as the metatag. When the parent is saved, or deleted, then their descendants are also.\n\n\n.. code-block:: python\n\n    async with db:\n\n        db_0 = await db.ametatag(\"process_0\")\n\n        assert db_0 is db.process_0\n\n\n        db_1 = await db.ametatag(\"process_1\")\n\n        assert db_1 is db.process_1\n\n\n    assert all(\n\n        isinstance(metatag, AsyncDatabase)\n\n        for metatag in [db_0, db_1]\n\n    )\n\n\nThey can contain their own sets of tags (and metatags). If metatags, or tags, are used as partitions that are accessed across distributed or concurrent contexts, it's highly recommended that each partition have only one distinct caller or object reference with write \u0026 cache access.\n\n.. code-block:: python\n\n    db = await AsyncDatabase(key)  # distinct object reference\n\n    assert db_0 is not db.process_0\n\n    assert db_1 is not db.process_1\n\n\n    async with db_0:\n\n        db_0[\"data\"] = b\"data added within process 0.\"\n\n    #      cache access                            disk read\n    #       vvvvvvvvvv                            vvvvvvvvvvv\n    assert db_0[\"data\"] == await db.process_0.aquery_tag(\"data\")\n\n\n    async with db_1:\n\n        db_1[\"data\"] = b\"data added within process 1.\"\n\n    #      cache access                            disk read\n    #       vvvvvvvvvv                            vvvvvvvvvvv\n    assert db_1[\"data\"] == await db.process_1.aquery_tag(\"data\")\n\n\nDeleting a metatag from an instance recursively deletes all of its own tags \u0026 metatags. To avoid inconsistencies, this should only be done from the original parent whose metatag reference ``is`` the metatag object with write \u0026 cache access.\n\n.. code-block:: python\n\n    metatag_manifest_file = db_0._root_path\n\n    assert metatag_manifest_file.is_file()\n\n\n    assert db_0 is db.process_0  # using the original parent object\n\n    async with db:\n\n        await db.adelete_metatag(\"process_0\")\n\n\n    db.metatags\n    \u003e\u003e\u003e {'process_1'}\n\n    assert not hasattr(db, \"process_0\")\n\n    assert not metatag_manifest_file.is_file()\n\n\n    #\n\n\n_`Basic Management` ............................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThere's a few settings \u0026 public methods on databases for users to manage their instances \u0026 data. This includes general utilities for saving \u0026 deleting databases to \u0026 from the filesystem, as well as fine-grained controls for how data is handled.\n\n.. code-block:: python\n\n    # The path attribute is set within the instance's __init__\n\n    # using a keyword-only argument. It's the directory where the\n\n    # instance will store all of its files.\n\n    db.path\n    \u003e\u003e\u003e PosixPath('site-packages/aiootp/aiootp/db')\n\n\n    # Write database changes to disk with transparent encryption -\u003e\n\n    await db.asave_database()\n\n\n    # Entering the instance's context also saves data to disk -\u003e\n\n    async with db:\n\n        print(\"Saving to disk...\")\n\n\n    # Delete a database from the filesystem -\u003e\n\n    await db.adelete_database()\n\n\nAs databases grow in the number of tags, metatags \u0026 the size of data within, it becomes desireable to load data from them as needed, instead of all at once into the cache during initialization. This is why the ``preload`` boolean keyword-only argument is set to ``False`` by default.\n\n.. code-block:: python\n\n    # Let's create some test values to show the impact preloading has -\u003e\n\n    async with (await AsyncDatabase(key, preload=True)) as db:\n\n        db[\"favorite_foods\"] = [\"justice\", \"community\"]\n\n        routines = await db.ametatag(\"exercise_routines\")\n\n        routines[\"gardening\"] = {\"days\": [\"monday\", \"wednesday\"]}\n\n        routines[\"swimming\"] = {\"days\": [\"thursday\", \"saturday\"]}\n\n\n    # Again, preloading into the cache is toggled off by default -\u003e\n\n    uncached_db = await AsyncDatabase(key)\n\n\n    # To retrieve elements, ``aquery_tag`` isn't necessary when\n\n    # preloading is used, since the tag is already in the cache -\u003e\n\n    async with uncached_db:\n\n        db[\"favorite_foods\"]\n        \u003e\u003e\u003e [\"justice\", \"community\"]\n\n        uncached_db[\"favorite_foods\"]\n        \u003e\u003e\u003e\n\n        value = await uncached_db.aquery_tag(\"favorite_foods\", cache=True)\n\n        assert value == [\"justice\", \"community\"]\n\n        assert value == uncached_db[\"favorite_foods\"]\n\n\n        # Metatags will be loaded, but their tags won't be -\u003e\n\n        uncached_db.exercise_routines[\"gardening\"]\n        \u003e\u003e\u003e\n\n        await uncached_db.exercise_routines.aquery_tag(\"gardening\", cache=True)\n        \u003e\u003e\u003e {\"days\": [\"monday\", \"wednesday\"]}\n\n        uncached_db.exercise_routines[\"gardening\"]\n        \u003e\u003e\u003e {\"days\": [\"monday\", \"wednesday\"]}\n\n\n        # But, tags can also be queried without caching their values,\n\n        value = await uncached_db.exercise_routines.aquery_tag(\"swimming\")\n        \u003e\u003e\u003e\n\n        value\n        \u003e\u003e\u003e {\"days\": [\"thursday\", \"saturday\"]}\n\n        uncached_db.exercise_routines[\"swimming\"]\n        \u003e\u003e\u003e\n\n\n        # However, changes to mutable values won't be transmitted to the\n\n        # database if they aren't retrieved from the cache -\u003e\n\n        value[\"days\"].append(\"sunday\")\n\n        value\n        \u003e\u003e\u003e {\"days\": [\"thursday\", \"saturday\", \"sunday\"]}\n\n        await uncached_db.exercise_routines.aquery_tag(\"swimming\")\n        \u003e\u003e\u003e {\"days\": [\"thursday\", \"saturday\"]}\n\n\n    #\n\n\n_`Encrypt / Decrypt` .............................. `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nAlthough databases handle encryption \u0026 decryption of files automatically, users may want to utilize their databases' keys to do manual cryptographic procedures. There are a few public functions which provide such functionality.\n\n.. code-block:: python\n\n    json_plaintext = {\"some\": \"JSON data can go here...\"}\n\n    bytes_plaintext = b\"some bytes plaintext goes here...\"\n\n    token_plaintext = b\"some token data goes here...\"\n\n    json_ciphertext = await db.ajson_encrypt(json_plaintext)\n\n    bytes_ciphertext = await db.abytes_encrypt(bytes_plaintext)\n\n    token_ciphertext = await db.amake_token(token_plaintext)\n\n\n    assert json_plaintext == await db.ajson_decrypt(json_ciphertext)\n\n    assert bytes_plaintext == await db.abytes_decrypt(bytes_ciphertext)\n\n    assert token_plaintext == await db.aread_token(token_ciphertext)\n\n\nFilenames \u0026 other associated data may be added to classify \u0026 tweak ciphertexts.\n\n.. code-block:: python\n\n    filename = \"grocery-list\"\n\n    groceries = [\"carrots\", \"taytoes\", \"rice\", \"beans\"]\n\n    ciphertext = await db.ajson_encrypt(\n        groceries, filename=filename, aad=b\"test\"\n    )\n\n    assert groceries == await db.ajson_decrypt(\n        ciphertext, filename=filename, aad=b\"test\"\n    )\n\n    await db.ajson_decrypt(\n        ciphertext, filename=\"wrong filename\", aad=b\"test\"\n    )\n    \u003e\u003e\u003e \"InvalidSHMAC: Invalid StreamHMAC hash for the given ciphertext.\"\n\n\nTime-based expiration checking is available for all ciphertexts.\n\n.. code-block:: python\n\n    from aiootp.asynchs import asleep\n\n\n    await asleep(6)\n\n    await db.ajson_decrypt(json_ciphertext, ttl=1)\n    \u003e\u003e\u003e \"TimestampExpired: Timestamp expired by \u003c5\u003e seconds.\"\n\n    await db.abytes_decrypt(bytes_ciphertext, ttl=1)\n    \u003e\u003e\u003e \"TimestampExpired: Timestamp expired by \u003c5\u003e seconds.\"\n\n    await db.aread_token(token_ciphertext, ttl=1)\n    \u003e\u003e\u003e \"TimestampExpired: Timestamp expired by \u003c5\u003e seconds.\"\n\n    try:\n\n        await db.abytes_decrypt(bytes_ciphertext, ttl=1)\n\n    except db.TimestampExpired as error:\n\n        assert error.expired_by == 5\n\n\n    #\n\n\n\n\n_`Chunky2048 \u0026 Slick256 Ciphers` .................. `Table Of Contents`_\n------------------------------------------------------------------------\n\n``Chunky2048`` \u0026 ``Slick256`` are novel cipher designs that use SHA3 extendable-output functions for key derivation \u0026 data authentication. They're distinct by being online, salt misuse-reuse resistant, fully context committing, \u0026 tweakable, AEADs.\n\n``Chunky2048`` is a stream cipher that processes blocks of data 256 bytes at a time. It accepts any length of key 64 bytes or larger, with a maximum internal entropy of 600 bytes.\n\n``Slick256`` on the other hand is a 32 byte combined stream \u0026 block cipher. Each round it XOR's an independent stream key with data, passes that sum through a keyed permutation, \u0026 XOR's the result with another independent stream key. It also accepts any length of key 64 bytes or larger, with a maximum internal entropy of 200 bytes.\n\nThey're each designed to be easy to use, difficult to misuse, \u0026 future-proof with very wide security margins.\n\n\n_`High-level Functions` .......................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThese premade recipes allow for the easiest usage of the ciphers. First, choose a cipher interface.\n\n.. code-block:: python\n\n    import aiootp\n\n\n    cipher = aiootp.Chunky2048(key)\n\n    cipher = aiootp.Slick256(key)\n\n\nSymmetric encryption of JSON data.\n\n.. code-block:: python\n\n    json_data = {\"account\": 33817, \"names\": [\"queen b\"], \"id\": None}\n\n    encrypted_json = cipher.json_encrypt(json_data, aad=b\"demo\")\n\n\n    assert json_data == cipher.json_decrypt(\n\n        encrypted_json, aad=b\"demo\", ttl=120\n\n    )\n\n\nSymmetric encryption of binary data.\n\n.. code-block:: python\n\n    binary_data = b\"some plaintext data...\"\n\n    encrypted_binary = cipher.bytes_encrypt(binary_data, aad=b\"demo\")\n\n\n    assert binary_data == cipher.bytes_decrypt(\n\n        encrypted_binary, aad=b\"demo\", ttl=30\n\n    )\n\n\nEncrypted URL-safe Base64 encoded tokens.\n\n.. code-block:: python\n\n    from collections import deque\n\n    from aiootp.generics import canonical_pack, canonical_unpack\n\n\n    token_data = deque([b\"user_id\", b\"session_id\", b\"secret_value\"])\n\n    encrypted_token = cipher.make_token(\n\n        canonical_pack(*token_data, int_bytes=1), aad=b\"demo\"\n\n    )\n\n\n    assert token_data == canonical_unpack(\n\n        cipher.read_token(encrypted_token, aad=b\"demo\", ttl=3600)\n\n    )\n\n\n    #\n\n\n_`High-level Generators` .......................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWith these generators, the online nature of the ``Chunky2048`` \u0026 ``Slick256`` ciphers can be utilized. This means that any arbitrary amount of data can be processed in streams of controllable, buffered chunks. These streaming interfaces automatically handle message padding \u0026 depadding, ciphertext validation \u0026 detection of out-of-order message blocks.\n\n\nEncryption:\n***********\n\nChoose a cipher interface.\n\n.. code-block:: python\n\n    from aiootp import Chunky2048, Slick256\n\n\n    cipher = Chunky2048(key)\n\n    cipher = Slick256(key)\n\n\nLet's imagine we are serving some data over a network. This will manage encrypting a stream of data.\n\n.. code-block:: python\n\n    receiver = SomeRemoteConnection(session).connect()\n\n    ...\n\n    stream = cipher.astream_encrypt(aad=session.transcript)\n\n\nWe'll have to send the salt \u0026 iv in some way.\n\n.. code-block:: python\n\n    receiver.transmit(salt=stream.salt, iv=stream.iv)\n\n\nNow we can buffer the plaintext we are going to encrypt.\n\n.. code-block:: python\n\n    for plaintext in receiver.upload.buffer(4 * stream.PACKETSIZE):\n\n        await stream.abuffer(plaintext)\n\n\n        # The stream will now produce encrypted blocks of ciphertext\n\n        # as well as the block ID which authenticates each block -\u003e\n\n        async for block_id, ciphertext in stream:\n\n            # The receiver needs both the block ID \u0026 ciphertext -\u003e\n\n            receiver.send_packet(block_id + ciphertext)\n\n\nOnce done with buffering-in the plaintext, the ``afinalize`` method is called so the remaining encrypted data will be flushed out of the buffer to the user.\n\n.. code-block:: python\n\n    async for block_id, ciphertext in stream.afinalize():\n\n        receiver.send_packet(block_id + ciphertext)\n\n\n    # Now we have to send the final authentication tag -\u003e\n\n    receiver.transmit(shmac=stream.shmac.result)\n\n\n    #\n\n\nDecryption:\n***********\n\nChoose the correct cipher interface.\n\n.. code-block:: python\n\n    from aiootp import Chunky2048, Slick256\n\n    cipher = Chunky2048(key)\n\n    cipher = Slick256(key)\n\n\nHere let's imagine we'll be downloading some data. The key, salt, aad \u0026 iv will need to be the same for both parties.\n\n.. code-block:: python\n\n    source = SomeRemoteConnection(session).connect()\n\n    ...\n\n    stream = cipher.astream_decrypt(\n\n        salt=source.salt, aad=session.transcript, iv=source.iv\n\n    )\n\n\nIf authentication succeeds, the plaintext is produced from the downloaded ciphertext buffer chunks.\n\n.. code-block:: python\n\n    for ciphertext in source.download.buffer(4 * stream.PACKETSIZE):\n\n        # Here stream.shmac.InvalidBlockID is raised if an invalid or\n\n        # out-of-order block is detected within the last 4 packets -\u003e\n\n        try:\n\n            await stream.abuffer(ciphertext)\n\n        except cipher.InvalidBlockID as auth_fail:\n\n            app.post_mortem(invalid_stream=auth_fail.failure_state)\n\n            raise auth_fail\n\n\n        async for plaintext in stream:\n\n            yield plaintext\n\n\nAfter all the ciphertext is downloaded, ``afinalize`` is called to finish processing the stream \u0026 flush out the plaintext. The final authenticity tag has to be checked once the stream is finished.\n\n.. code-block:: python\n\n    async for plaintext in stream.afinalize():\n\n        yield plaintext\n\n    await stream.shmac.atest_shmac(source.shmac)\n\n\n    #\n\n\n_`Chunky2048 Algorithm` ........................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n\n.. code-block:: bash\n\n    '''\n\n    S = SHMAC KDF\n    L = Left KDF\n    R = Right KDF\n    P = 256-byte plaintext block\n    C = 256-byte ciphertext block\n    O = Two concatenated 168-byte SHMAC KDF outputs\n    K_L, K_R = the two 168-byte left \u0026 right KDF outputs\n\n    Each block, except for the first, is processed as such:\n\n     _____________________________________\n    |                                     |\n    |    Algorithm Diagram: Encryption    |\n    |_____________________________________|\n                                       ___       ___\n                                        |         |\n                                        |    ___ _|_\n                                        |     |   |\n                             -----      |     |   |\n                O[0::2] ---\u003e|  L  |---\u003eK_L----⊕--\u003e|\n               /             -----      |     |   |           /\n         -----/                         |     |   |     -----/\n        |  S  |                        ---    P   C    |  S  |\n         -----\\                         |     |   |     -----\\\n           ^   \\             -----      |     |   |       ^   \\\n           |    O[1::2] ---\u003e|  R  |---\u003eK_R----⊕--\u003e|       |\n           |                 -----      |     |   |       |\n           |                            |    _|_ _|_      |\n           |                            |         |       |\n           |                           _|_       _|_      |\n           |                                      |       |\n    --------                                      ---------\n     _____________________________________\n    |                                     |\n    |    Algorithm Diagram: Decryption    |\n    |_____________________________________|\n                                       ___   ___\n                                        |     |\n                                        |    _|_ ___\n                                        |     |   |\n                             -----      |     |   |\n                O[0::2] ---\u003e|  L  |---\u003eK_L----⊕--\u003e|\n               /             -----      |     |   |           /\n         -----/                         |     |   |     -----/\n        |  S  |                        ---    C   P    |  S  |\n         -----\\                         |     |   |     -----\\\n           ^   \\             -----      |     |   |       ^   \\\n           |    O[1::2] ---\u003e|  R  |---\u003eK_R----⊕--\u003e|       |\n           |                 -----      |     |   |       |\n           |                            |    _|_ _|_      |\n           |                            |     |           |\n           |                           _|_   _|_          |\n           |                                  |           |\n    --------                                  -------------\n\n\n    '''\n\n\n_`Slick256 Algorithm` ............................. `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n\n.. code-block:: bash\n\n    '''\n\n    S = SHMAC KDF\n    π = Permutation()\n    P = 32-byte plaintext block\n    C = 32-byte ciphertext block\n    K_I, K_O, D = (K_i[:32], K_i[32:64], K_i[64:168])\n\n    Each block is processed as such:\n\n     _____________________________________\n    |                                     |\n    |    Algorithm Diagram: Encryption    |\n    |_____________________________________|\n\n                 K_I-------⊕--------       P\n                /          ^       |       |                     /\n               /           |       v       |                    /\n         -----/            P     -----     v              -----/\n    ---\u003e|  S  |                 |  π  |   (P ║ C ║ D)---\u003e|  S  |\n         -----\\                  -----         ^          -----\\\n               \\                   |           |                \\\n                \\                  v           |                 \\\n                 K_O---------------⊕----------\u003eC\n\n     _____________________________________\n    |                                     |\n    |    Algorithm Diagram: Decryption    |\n    |_____________________________________|\n\n                 K_I---------------⊕------\u003eP\n                /                  ^       |                     /\n               /                   |       |                    /\n         -----/                  -----     v              -----/\n    ---\u003e|  S  |                 |  π  |   (P ║ C ║ D)---\u003e|  S  |\n         -----\\            C     -----         ^          -----\\\n               \\           |       ^           |                \\\n                \\          v       |           |                 \\\n                 K_O-------⊕--------           C\n\n\n    '''\n\n\n\n\n_`Passcrypt` .............................. `Table Of Contents`_\n------------------------------------------------------------------------\n\nThe ``Passcrypt`` algorithm is a data independent memory \u0026 computationally hard password-based key derivation function. It's built from a single primitive, the SHAKE-128 extendable output function from the SHA-3 family. Its resource costs are measured by three parameters: ``mb``, which represents an integer number of Mebibytes (MiB); ``cpu``, which is a linear integer measure of computational complexity \u0026 the number of iterations of the algorithm over the memory cache; and ``cores``, which is an integer which directly assigns the number of separate processes that will be pooled to complete the algorithm. The number of bytes of the output tag are decided by the integer ``tag_size`` parameter. And, the number of bytes of the automatically generated ``salt`` are decided by the integer ``salt_size`` parameter.\n\n\n_`Hashing \u0026 Verifying Passphrases` .......................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n\nBy far, the dominating measure of difficulty for ``Passcrypt`` is determined by the ``mb`` Mebibyte memory cost. It's recommended that increases to desired difficulty are first translated into higher ``mb`` values, where resource limitations of the machines executing the algorithm permit. If more difficulty is desired than can be obtained by increasing ``mb``, then increases to the ``cpu`` parameter should be used. The higher this parameter is the less likely an adversary is to benefit from expending less than the intended memory cost, \u0026 increases the execution time \u0026 complexity of the algorithm. The final option that should be considered, if still more difficulty is desired, is to lower the ``cores`` parallelization parameter, which will just cause each execution to take longer to complete.\n\n\nThe class accepts an optional (but recommended) static \"pepper\" which is applied as additional randomness to all hashes computed by the class. It's a secret random bytes value of any size that is expected to be stored somewhere inaccessible by the database which contains the hashed passphrases.\n\n.. code-block:: python\n\n    from aiootp import Passcrypt, hash_bytes\n\n\n    with open(SECRET_PEPPER_PATH, \"rb\") as pepper_file:\n\n        Passcrypt.PEPPER = pepper_file.read()\n\n\nWhen preparing to hash passphrases, it's a good idea to use any \u0026 all of the static data / credentials available which are specific to the context of the registration.\n\n.. code-block:: python\n\n    APPLICATION = b\"my-application-name\"\n\n    PRODUCT = b\"the-product-being-accessed-by-this-registration\"\n\n    STATIC_CONTEXT = [APPLICATION, PRODUCT, PUBLIC_CERTIFICATE]\n\n\nA ``Passcrypt`` instance is initialized with the desired difficulty settings.\n\n.. code-block:: python\n\n    pcrypt = Passcrypt(\n        mb=1024,      # 1 GiB\n        cpu=2,        # 2 iterations\n        cores=8,      # 8 parallel cores\n        tag_size=16,  # 16-byte hash\n    )\n\n\nNow we can start hashing any user information that arrives.\n\n.. code-block:: python\n\n    username = form[\"username\"].encode()\n\n    passphrase = form[\"passphrase\"].encode()\n\n    email_address = form[\"email_address\"].encode()\n\n\nThe ``hash_bytes`` function can then be used to automatically encode then hash the multi-input data so as to prevent the chance of canonicalization (\u0026/or length extension) attacks.\n\n.. code-block:: python\n\n    aad = hash_bytes(*STATIC_CONTEXT, username, email_address)\n\n    hashed_passphrase = pcrypt.hash_passphrase(passphrase, aad=aad)\n\n    assert type(hashed_passphrase) is bytes\n\n    assert len(hashed_passphrase) == 38\n\n\nLater, a hashed passphrase can be used to authenticate a user.\n\n.. code-block:: python\n\n    untrusted_username = form[\"username\"].encode()\n\n    untrusted_passphrase = form[\"passphrase\"].encode()\n\n    untrusted_email_address = form[\"email_address\"].encode()\n\n    aad = hash_bytes(\n\n        *STATIC_CONTEXT, untrusted_username, untrusted_email_address\n\n    )\n\n    try:\n\n        pcrypt.verify(\n\n            hashed_passphrase, untrusted_passphrase, aad=aad, ttl=3600\n\n        )\n\n    except pcrypt.InvalidPassphrase as auth_fail:\n\n        # If the passphrase does not hash to the same value as the\n\n        # stored hash, then this exception is raised \u0026 can be handled\n\n        # by the application -\u003e\n\n        app.post_mortem(error=auth_fail)\n\n    except pcrypt.TimestampExpired as registration_expired:\n\n        # If the timestamp on the stored hash was created more than\n\n        # ``ttl`` seconds before the current time, then this exception\n\n        # is raised. This is helpful for automating registrations which\n\n        # expire after a certain amount of time, which in this case was\n\n        # 1 hour -\u003e\n\n        app.post_mortem(error=registration_expired)\n\n    else:\n\n        # If no exception was raised, then the user has been authenticated\n\n        # by their passphrase, username, email address \u0026 the context of\n\n        # the registration -\u003e\n\n        app.login_user(username, email_address)\n\n\n    #\n\n\n_`Passcrypt Algorithm Overview` .......................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nBy being secret-independent, ``Passcrypt`` is resistant to side-channel attacks. This implementation is also written in pure python. Significant attention was paid to design the algorithm so as to suffer minimally from the performance inefficiencies of python, since doing so would help to equalize the cost of computation between regular users \u0026 dedicated attackers with custom hardware / software. Below is a diagram that depicts how an example execution works:\n\n.. code-block:: bash\n\n    \"\"\"\n\n           ___________________ # of rows ___________________\n          |                                                 |\n          |              initial memory cache               |\n          |  row  # of columns == 2 * max([1, cpu // 2])    |\n          |   |   # of rows == ⌈1024*1024*mb/168*columns⌉   |\n          v   v                                             v\n    column|---'-----------------------------------------'---| the initial cache\n    column|---'-----------------------------------------'---| of size ~`mb` is\n    column|---'-----------------------------------------'---| built very quickly\n    column|---'-----------------------------------------'---| using SHAKE-128.\n    column|---'-----------------------------------------'---| each (row, column)\n    column|---'-----------------------------------------'---| coordinate holds\n    column|---'-----------------------------------------'---| one element of\n    column|---'-----------------------------------------'---| 168-bytes.\n                                                        ^\n                                                        |\n                           reflection                  row\n                          \u003c-   |\n          |--------------------'-------'--------------------| each row is\n          |--------------------'-------'--------------------| hashed then has\n          |--------------------'-------'--------------------| a new 168-byte\n          |--------------------'-------'--------------------| digest overwrite\n          |--------------------'-------'--------------------| the current pointer\n          |--------------------'-------'--------------------| in an alternating\n          |--------------------Xxxxxxxx'xxxxxxxxxxxxxxxxxxxx| sequence, first at\n          |oooooooooooooooooooo'oooooooO--------------------| the index, then at\n                                       |   -\u003e                 its reflection.\n                                     index\n\n\n          |--'-------------------------------------------'--| this continues\n          |--'-------------------------------------------'--| until the entire\n          |--'-------------------------------------------Xxx| cache has been\n          |ooO-------------------------------------------'--| overwritten.\n          |xx'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'xx| a single `shake_128`\n          |oo'ooooooooooooooooooooooooooooooooooooooooooo'oo| object (H) is used\n          |xx'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'xx| to do all of the\n          |oo'ooooooooooooooooooooooooooooooooooooooooooo'oo| hashing.\n             |   -\u003e                                 \u003c-   |\n           index                                     reflection\n\n\n          |xxxxxxxxxxx'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| finally, the whole\n          |ooooooooooo'ooooooooooooooooooooooooooooooooooooo| cache is quickly\n          |xxxxxxxxxxx'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| hashed `cpu` + 2\n          |ooooooooooo'ooooooooooooooooooooooooooooooooooooo| number of times.\n          |Fxxxxxxxxxx'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| after each pass an\n          |foooooooooo'ooooooooooooooooooooooooooooooooooooo| 84-byte digest is\n          |fxxxxxxxxxx'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| inserted into the\n          |foooooooooo'ooooooooooooooooooooooooooooooooooooo| cache, ruling out\n                      |   -\u003e                                  hashing state cycles.\n                      | hash cpu + 2 # of times               Then a `tag_size`-\n                      v                                       byte tag is output.\n                  H(cache)\n\n          tag = H.digest(tag_size)\n\n\n    \"\"\"\n\n\n\n\n_`X25519 \u0026 Ed25519` ............................... `Table Of Contents`_\n------------------------------------------------------------------------\n\nAsymmetric curve 25519 tools are available from these high-level interfaces over the ``cryptography`` package.\n\n\n_`X25519` ......................................... `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nElliptic Curve25519 Diffie-Hellman key exchange protocols.\n\n\nBasic Elliptic Curve Diffie-Hellman\n***********************************\n\n.. code-block:: python\n\n    from aiootp import X25519, DomainKDF, GUID, Domains\n\n\n    guid = GUID().new()\n\n    my_ecdhe_key = X25519().generate()\n\n    yield guid, my_ecdhe_key.public_bytes  # send this to Bob\n\n    raw_shared_secret = my_ecdhe_key.exchange(bobs_public_key)\n\n    shared_kdf = DomainKDF(  # Use this to create secret shared keys\n\n        Domains.ECDHE,\n\n        guid,\n\n        bobs_public_key,\n\n        my_ecdhe_key.public_bytes,\n\n        key=raw_shared_secret,\n\n    )\n\n\nTriple ECDH Key Exchange:\n*************************\n\n.. code-block:: bash\n\n    '''\n     _____________________________________\n    |                                     |\n    |          Protocol Diagram:          |\n    |_____________________________________|\n\n            -----------------          |         -----------------\n            |  Client-side  |          |         |  Server-side  |\n            -----------------          |         -----------------\n                                       |\n    key = X25519().generate()          |         X25519().generate() = key\n                                       |\n    client = key.dh3_client()          |           key.public_bytes = id_s\n                                       |\n    id_c, eph_c = client.send(id_s) ------\u003e\n                                       |\n                                       |         key.dh3_server() = server\n                                       |\n                                       | server.receive(id_c, eph_c) = kdf\n                                       |\n                                    \u003c------          server.send() = eph_s\n                                       |\n    kdf = client.receive(eph_s)        |\n                                       |\n\n    '''\n\n\nDouble ECDH Key Exchange:\n*************************\n\n.. code-block:: bash\n\n    '''\n     _____________________________________\n    |                                     |\n    |          Protocol Diagram:          |\n    |_____________________________________|\n\n            -----------------          |         -----------------\n            |  Client-side  |          |         |  Server-side  |\n            -----------------          |         -----------------\n                                       |\n                                       |         X25519().generate() = key\n                                       |\n    client = X25519.dh2_client()       |           key.public_bytes = id_s\n                                       |\n    eph_c = client.send(id_s)       ------\u003e\n                                       |\n                                       |         key.dh2_server() = server\n                                       |\n                                       |       server.receive(eph_c) = kdf\n                                       |\n                                    \u003c------          server.send() = eph_s\n                                       |\n    kdf = client.receive(eph_s)        |\n                                       |\n\n    '''\n\n\n\n\n_`Ed25519` ........................................ `Table Of Contents`_\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nEdwards curve 25519 signing \u0026 verification.\n\n.. code-block:: python\n\n    from aiootp import Ed25519\n\n\n    # In a land, long ago -\u003e\n\n    alices_key = Ed25519().generate()\n\n    internet.send(alices_key.public_bytes)\n\n\n    # Alice wants to sign a document so that Bob can prove she wrote it.\n\n    # So, Alice sends the public key bytes of the key she wants to\n\n    # associate with her identity, the document \u0026 the signature -\u003e\n\n    document = b\"DesignDocument.cad\"\n\n    signed_document = alices_key.sign(document)\n\n    message = {\n        \"document\": document,\n        \"signature\": signed_document,\n        \"public_key\": alices_key.public_bytes,\n    }\n\n    internet.send(message)\n\n\n    # In a land far away -\u003e\n\n    alices_message = internet.receive()\n\n    # Bob sees the message from Alice! Bob already knows Alice's public\n\n    # key \u0026 she has reason believe it is genuinely Alice's. So, she'll\n\n    # import Alice's known public key to verify the signed document -\u003e\n\n    assert alices_message[\"public_key\"] == alices_public_key\n\n    alice_verifier = Ed25519().import_public_key(alices_public_key)\n\n    alice_verifier.verify(\n        alices_message[\"signature\"], alices_message[\"document\"]\n    )\n\n    internet.send(b\"Beautiful work, Alice! Thanks ^u^\")\n\nThe verification didn't throw an exception! So, Bob knows the file was signed by Alice.\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frmlibre%2Faiootp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frmlibre%2Faiootp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frmlibre%2Faiootp/lists"}