{"id":50299105,"url":"https://github.com/umbrellaleaf5/cesar_len_pass_vault","last_synced_at":"2026-05-31T21:00:37.384Z","repository":{"id":360569738,"uuid":"1250722703","full_name":"UmbrellaLeaf5/cesar_len_pass_vault","owner":"UmbrellaLeaf5","description":"Password vault with manual Yandex Disk sync. Encrypts a JSON vault using a hardened Caesar cipher (via cesar_len_key)","archived":false,"fork":false,"pushed_at":"2026-05-28T11:12:56.000Z","size":302,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-28T11:23:57.266Z","etag":null,"topics":["cesar-cypher","crypter","kivy","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/UmbrellaLeaf5.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-26T23:00:36.000Z","updated_at":"2026-05-28T11:13:04.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/UmbrellaLeaf5/cesar_len_pass_vault","commit_stats":null,"previous_names":["umbrellaleaf5/cesar_len_pass_vault"],"tags_count":0,"template":false,"template_full_name":"UmbrellaLeaf5/template_opencode_agented","purl":"pkg:github/UmbrellaLeaf5/cesar_len_pass_vault","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UmbrellaLeaf5%2Fcesar_len_pass_vault","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UmbrellaLeaf5%2Fcesar_len_pass_vault/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UmbrellaLeaf5%2Fcesar_len_pass_vault/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UmbrellaLeaf5%2Fcesar_len_pass_vault/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/UmbrellaLeaf5","download_url":"https://codeload.github.com/UmbrellaLeaf5/cesar_len_pass_vault/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/UmbrellaLeaf5%2Fcesar_len_pass_vault/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33748607,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["cesar-cypher","crypter","kivy","python"],"created_at":"2026-05-28T11:02:06.033Z","updated_at":"2026-05-31T21:00:37.375Z","avatar_url":"https://github.com/UmbrellaLeaf5.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CesarLen PassVault\n\n[![Python](https://img.shields.io/badge/Python-3.12+-blue?logo=python)](https://python.org)\n[![Kivy](https://img.shields.io/badge/Kivy-2.3+-black?logo=kivy)](https://kivy.org)\n[![python-dotenv](https://img.shields.io/badge/dotenv-1.0+-yellow)](https://github.com/theskumar/python-dotenv)\n[![YandexDisk](https://img.shields.io/badge/Yandex.Disk-REST-red)](https://yandex.ru/dev/disk/)\n[![License](https://img.shields.io/badge/License-Unlicense-lightgrey)](https://unlicense.org)\n\n\u003c!-- [![Tests](https://github.com/UmbrellaLeaf5/cesar_len_pass_vault/workflows/Tests/badge.svg)](https://github.com/UmbrellaLeaf5/cesar_len_pass_vault/actions/workflows/tests.yml)\n[![Ruff](https://github.com/UmbrellaLeaf5/cesar_len_pass_vault/workflows/Ruff/badge.svg)](https://github.com/UmbrellaLeaf5/cesar_len_pass_vault/actions/workflows/ruff.yml)\n[![Pyright](https://github.com/UmbrellaLeaf5/cesar_len_pass_vault/workflows/Pyright/badge.svg)](https://github.com/UmbrellaLeaf5/cesar_len_pass_vault/actions/workflows/pyright.yml) --\u003e\n\nA **Kivy-based GUI password vault** that stores encrypted JSON on\n[Yandex.Disk](https://yandex.ru/dev/disk/). No local files - the vault\nexists only as an encrypted blob in the cloud.\n\n## Dual encryption\n\nEvery vault is stored **twice** with independent algorithms so that a drifting\nfloating-point formula on one platform never locks you out:\n\n| Version | Library                                                             | Cipher                                                                                                       |\n| ------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |\n| Primary | [**cesar_len_key**](https://github.com/UmbrellaLeaf5/cesar_len_key) | `CryptedLines` - word-level Caesar shuffle with trig‑based key expansion                                     |\n| Backup  | `cipher_wrapper.py`                                                 | Multi‑round Caesar with SHA‑256 key stretching, HMAC subkeys, and integer‑only shift computation (no floats) |\n\nBoth versions share the same master password but derive keys differently.\nThey also use independent cryptographic salts, so the same plaintext produces\ncompletely different ciphertexts for each path.\n\nThe primary cipher (`cesar_len_key`) is fast and compact. The backup cipher\nis deliberately over‑engineered - multiple rounds, derived subkeys, forbidden\nzero‑shifts - specifically to avoid the floating‑point pitfalls that could\ncause the primary cipher to produce different output on different hardware.\n\n## How it works - user experience\n\n### 0. First launch - setup\n\nOn the very first launch (or when `.env` is missing) the app opens the\n**Setup** screen. Enter your **Yandex.Disk OAuth token** and the **remote\npath** where the vault should live, then press **Save \u0026 Continue**. The app\nwrites the settings to `.env` and proceeds to the Unlock screen.\n\nOn subsequent launches the Setup screen is skipped - your credentials are\nreused from `.env`.\n\nOn Android, settings are stored in the app's private `user_data_dir` as\n`settings.json` (Android does not use `.env` files).\n\n### 1. Unlock\n\nThe app opens to a dark unlock screen with a single password field. Type your\nmaster password and press **Unlock** (or Enter).\n\nBehind the scenes the app immediately reaches Yandex.Disk, downloads the\nencrypted vault, and tries to decrypt it with the password you just entered.\nIf the password is correct, you land on the main editor screen with your data\nalready displayed - no extra clicks needed.\n\nIf the password is **wrong**, the screen background turns pale red and a\nmessage says \"Invalid master password\". You can retry immediately.\n\n### 2. Main editor\n\nOnce unlocked you see:\n\n- A **toolbar** at the top with four buttons: **Download**, **Upload**,\n  **+ Entry**, and a **settings gear** (⋆).\n- A full‑screen **text editor** showing your vault as formatted JSON.\n- A **status bar** at the bottom with timestamps and entry counts.\n\nThe toolbar adapts to the current state:\n\n| State                               | Download | Upload | + Entry |\n| ----------------------------------- | -------- | ------ | ------- |\n| **Empty** - nothing loaded yet      | +        | -      | -       |\n| **Loaded** - primary vault is shown | +        | +      | +       |\n| **Loading** - network in progress   | -        | -      | -       |\n| **Split** - two editors visible     | +        | +      | +       |\n\n### 3. Adding entries\n\nPress **+ Entry** to open a popup with four fields: **Service**, **Login**,\n**Password**, and **Notes**. Fill in at least Service and Login, then press\n**Save**. The entry is appended as a JSON object to the editor. Press\n**Upload** to push the updated vault to the cloud.\n\n### 4. Split mode - comparing ciphers\n\nThe vault is encrypted **twice** with different algorithms (see\n[Dual encryption](#dual-encryption) below). Normally you only see the primary\nversion. Press the **gear icon** and choose **Download Backup** to load the\nbackup version alongside the primary one - two editors side by side.\n\nThis is useful when the primary cipher fails to decode correctly (e.g. due to\nfloating‑point quirks on a different CPU). You can inspect both versions and\npick the one you trust.\n\nIn split mode **+ Entry** adds to the right (backup) editor. On **Upload** the\napp checks whether the two editors differ. If they do, a popup asks which\nversion to keep - then **both** cloud files are synchronised with the same\nchosen content.\n\n### 5. Error recovery\n\nIf a network call fails, the editor drops back to the **Empty** state: a blank\nJSON editor with only the **Download** button active. You can retry at any\ntime.\n\n## State machine\n\nThe vault screen behaves as a deterministic state machine driven by the\n`VaultState` enum:\n\n```\n         ┌──────────────┐\n         │    EMPTY     │  download / upload disabled except Download\n         │              │  editor: readonly, empty\n         └──────┬───────┘\n                │ download (primary)\n                │\n         ┌──────▼───────┐\n  ┌──────│    LOADED    │  all operations enabled\n  │      │              │  editor: editable, primary vault shown\n  │      └──────┬───────┘\n  │             │ download (backup)\n  │             │\n  │      ┌──────▼───────┐\n  │      │    SPLIT     │  two editors side by side\n  │      │              │  primary (left) + backup (right)\n  │      └──────┬───────┘\n  │             │ download (primary) - exits split\n  │             │ upload - saves both, stays in split\n  │             │ add entry - appends to backup editor\n  │             │\n  │      ┌──────▼───────┐\n  │      │   LOADING    │  all buttons disabled while network\n  │      │              │  operation is in progress\n  │      └──────┬───────┘\n  │             │ success → LOADED or SPLIT\n  │             │ error → EMPTY\n  └─────────────┘\n```\n\nEvery transition goes through a single method (`_update_ui_by_state`) that\nupdates toolbar availability, editor read‑only flag, and split‑editor\nvisibility in one atomic step.\n\n## Quick start\n\n```bash\ngit clone https://github.com/UmbrellaLeaf5/cesar_len_pass_vault\ncd cesar_len_pass_vault\nuv sync\nuv run cesar-vault\n```\n\nOn first launch, the **Setup** screen will ask for your Yandex.Disk token\nand remote path. The settings are saved to `.env` and reused automatically.\n\n### How to get a Yandex.Disk OAuth token\n\n1. Go to [Yandex OAuth](https://oauth.yandex.ru/) and create a new application.\n2. Grant it the **Yandex.Disk REST API** permission (`cloud_api:disk`).\n3. Copy the token and paste it into `YA_TOKEN=` in your `.env` file.\n\n### `.env` reference\n\n| Variable      | Default  | Description                               |\n| ------------- | -------- | ----------------------------------------- |\n| `YA_TOKEN`    | -        | Yandex.Disk OAuth token (**required**)    |\n| `REMOTE_PATH` | -        | Primary vault path on Disk (**required**) |\n| `SALT_SIZE`   | `32`     | Salt length in bytes                      |\n| `ITERATIONS`  | `100000` | SHA‑256 key stretching rounds             |\n| `ROUNDS`      | `3`      | Encryption rounds for backup cipher       |\n\n`BACKUP_REMOTE_PATH` is computed automatically as `REMOTE_PATH + \".backup\"`.\nSpecify it explicitly in `.env` only if you need a custom backup location\n(backward compatibility is preserved).\n\n\u003e **Tip:** All values can be set through the **Setup** screen on first launch.\n\u003e Manual `.env` editing is optional.\n\n## Building\n\n### Windows `.exe`\n\n```bash\nuv run pyinstaller pyinstaller.spec\n# Output: dist/CesarLen-PassVault/\n```\n\nThe spec uses `--onedir` with the ANGLE backend. All `.kv` and image files\nare bundled automatically.\n\n### Android `.apk`\n\n```bash\npip install buildozer cython\nbuildozer android release\n# Output: bin/*.apk\n```\n\nRequires Linux. On Android the settings are saved as `settings.json` in the\napp's private directory (no `.env` files).\n\n### Automated releases\n\nCI workflows (`.github/workflows/`) trigger on any tag:\n\n- `build-exe.yml` - Windows `.exe` (zip archive)\n- `build-apk.yml` - Android `.apk`\n\n## License\n\n[Unlicense](LICENSE) - public domain.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fumbrellaleaf5%2Fcesar_len_pass_vault","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fumbrellaleaf5%2Fcesar_len_pass_vault","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fumbrellaleaf5%2Fcesar_len_pass_vault/lists"}