{"id":21206205,"url":"https://github.com/rtmigo/dmk_py","last_synced_at":"2025-07-10T08:32:55.984Z","repository":{"id":57423033,"uuid":"374848143","full_name":"rtmigo/dmk_py","owner":"rtmigo","description":"Experimental encryption app","archived":false,"fork":false,"pushed_at":"2023-09-14T17:58:42.000Z","size":3377,"stargazers_count":3,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"staging","last_synced_at":"2025-06-04T01:44:41.708Z","etag":null,"topics":["cli","cryptography","deniable-encryption","encrypted-store","file-encryption","information-security","linux","macos","obfuscation","package","password-manager","password-storage","plausible-deniability","posix","python","windows"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/dmk","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rtmigo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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}},"created_at":"2021-06-08T01:42:56.000Z","updated_at":"2025-03-06T16:00:03.000Z","dependencies_parsed_at":"2024-11-20T20:54:50.765Z","dependency_job_id":"de789e05-a7cc-4fcf-aac1-f919c38000fd","html_url":"https://github.com/rtmigo/dmk_py","commit_stats":{"total_commits":311,"total_committers":2,"mean_commits":155.5,"dds":0.07395498392282962,"last_synced_commit":"0bc391e21b675a72e52ac2aef2d5b95e4adeb017"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/rtmigo/dmk_py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtmigo%2Fdmk_py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtmigo%2Fdmk_py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtmigo%2Fdmk_py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtmigo%2Fdmk_py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rtmigo","download_url":"https://codeload.github.com/rtmigo/dmk_py/tar.gz/refs/heads/staging","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rtmigo%2Fdmk_py/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264431661,"owners_count":23607290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["cli","cryptography","deniable-encryption","encrypted-store","file-encryption","information-security","linux","macos","obfuscation","package","password-manager","password-storage","plausible-deniability","posix","python","windows"],"created_at":"2024-11-20T20:54:46.889Z","updated_at":"2025-07-10T08:32:55.642Z","avatar_url":"https://github.com/rtmigo.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Generic badge](https://img.shields.io/badge/Maturity-Experimental-red.svg)\n](#)\n[![Generic badge](https://img.shields.io/badge/Python-3.7–3.11-blue.svg)](#)\n[![Generic badge](https://img.shields.io/badge/OS-Linux%20|%20macOS%20|%20Windows-blue.svg)](#)\n\n# [dmk: dark matter keeper](https://github.com/rtmigo/dmk_py#readme)\n\n**THIS IS EXPERIMENTAL CODE. THE FILE FORMAT MAY CHANGE**\n\n`dmk` stores files, passwords or other private data in an encrypted **vault file**.\n\nEach **entry** has a **secret name**, that decrypts the entry.\nIt reveals nothing about other entries, even whether they exist.\n\nNo master password. No table of contents. No way to determine the number entries. \nNo way to access all entries at once. \n\nThe vault file is mostly unidentifiable data. Secret name discovers\nthe data of particular entry. The rest of the data remain dark matter.\n\n# Install\n\n``` \n$ pip3 install dmk\n```\n\n# Secret names\n\nA secret name serves as both:\n\n- the name of the entry\n- the password\n\nIt is a secret. And it must be unique.\n\nFor example, information about a credit card credentials can be stored under name\n`\"crEd1tcard\"` or `\"visa_secret123\"`.\n\nLonger secret names mean better encryption.\n\n# Save and read text\n\nWhen called without parameters, the `get` and `set` commands query for all \nvalues interactively:\n\n``` \n$ dmk set\n\nSecret name: secRet007\nRepeat secret name: secRet007 \nText: My darling's jokes are not so funny\n```\n\n``` \n$ dmk get\n\nSecret name: secRet007\n \nMy darling's jokes are not so funny\n```\n\nInteractive input is optional. You can get by with one line:\n\n``` \n$ dmk set -e secRet007 -t \"My darling's jokes are not so funny\"\n```\n\n``` \n$ dmk get -e secRet007\n\nMy darling's jokes are not so funny\n```\n\n\n\n# Save and read file\n\nRead data from a `source.doc` and save it as encrypted entry `secRet007`\n\n```  \n$ dmk set -e secRet007 /my/docs/source.doc\n```\n\nDecrypt the entry `secRet007` and write the result to `target.doc`\n\n``` bash\n$ dmk get -e secRet007 /my/docs/target.doc\n```\n\nThe `-e` parameter is optional. If it is not specified, the value will be\nprompted for interactive input.\n\nAdd dummy data\n==============\n\nPart of the vault file contains dummy data. This data cannot be decrypted.\nDummy data only increases the size of the storage, thus hiding the amount \nof real data.\n\nEach time the file is updated, a random amount of dummy data is added and removed. \nThe change can be up to 5% of the file size.\n\nYou can also add dummy data manually, to make sure the file is big enough.\n\nMake the vault file 2 megabytes larger:\n\n```\ndmk dummy 2M\n```\n\n\nMake the vault file 500 kilobytes larger:\n\n```\ndmk dummy 500K\n```\n\n\nKeep in mind:\n\n- Dummy data added in this way cannot be removed\n- Vault speed linearly depends on its size. If you increase the vault 10 times, \n  then the search for data in it will go 10 times slower\n\nVault location\n==============\n\nEntries will be stored in a file.\n\nYou can check the current vault file location with `vault` command:\n\n```\n$ dmk vault\n```\nOutput:\n```\n/home/username/vault.dmk\n```\n\nBy default, it is `vault.dmk` in the current user's `$HOME` directory.\n\n--------------------------------------------------------------------------------\n\nThe `-v` parameter overrides the location for a single run.\n\n```\n$ dmk -v /path/to/myfile.data vault\n```\n\nOutput:\n```\n/path/to/myfile.data\n```\n\nThe parameter can be used with any commands:\n\n```\n$ dmk -v /path/to/myfile.data set \n$ dmk -v /path/to/myfile.data get \n```\n\n--------------------------------------------------------------------------------\n\nThe `$DMK_VAULT_FILE` environment variable overrides the default location:\n\n``` \n$ export DMK_VAULT_FILE=/path/to/myfile.data\n$ dmk vault  \n```\nOutput:\n```\n/path/to/myfile.data\n```\n\nWhile `$DMK_VAULT_FILE` is set all the commands will use `myfile.data`:\n\n```\n$ dmk set   # set to myfile.data \n$ dmk get   # get from myfile.data\n```\n\n# Under the hood\n\n- Entries are encrypted \n- Number of entries cannot be determined\n- File format is unidentifiable\n\n## Size obfuscation\n\nThe vault file stores all data within multiple fixed-size blocks.\n\nSmall entries are padded so they become block-sized. Large entries are split and\npadded to fit into multiple blocks. In the end, they are all just a lot of\nblocks.\n\nA block gives absolutely no information for someone who does not own the\nsecret name. All non-random data is either hashed or encrypted. The size of padding\nis unknown.\n\nThe number of blocks is no secret. Their contents are secret.\n\n- The number of blocks is random. Many blocks are dummy. They are\n  indistinguishable from real data, but do not contain anything meaningful\n\n- The information about which entry the block belongs to is cryptographically\n  protected. It is impossible to even figure out if two blocks belong to the same\n  entry\n\n- Random actions are taken every time the vault is updated: some dummy blocks are\n  added, and some are removed\n\nThus, **number and size of entries cannot be determined** by the size of the\nvault file or number of blocks.\n\nOnly the following is known:\n- The payload is smaller than the file size\n- The number of entries is less than the number of blocks\n\nBy the way, the file may contain zero entries.\n\n## File obfuscation\n\nThe vault file format is **indistinguishable from random data**.\n\nThe file has no signatures, no header, no constant bytes (or even bits), no\nblock boundaries. File size will not give clues: the file is randomly padded\nwith a size that is not a multiple of a block.\n\nThe only predictable part of the file is a version identifier encoded in\nthe first two bytes. But the similar \"version number\" can be found literally \nin every fourth file in the world. Those two bytes are not even constant.\n\n## Еncryption\n\n1) **URandom** creates 38-bytes **salt** when we initialize the vault file. The\n   salt is saved openly in the file. This salt never changes. It is required for\n   any other actions on the vault.\n\n2) **Argon2id** (memory 128 MiB, iterations 4, parallelism 8) derives \n   256-bit **private key** from salted (1) secret name.\n   \n3) 96-bit urandom **block nonce** is generated for each block.\n\n4) To indicate that a block belongs to the secret name, we add a 256-bit hash\n   to the beginning of the block. It is a **Blake2s** hash of private\n   key (2) + block nonce (3).\n   \n   During the read, for each block, we compute this hash again. If the value \n   matches, we [decide](https://stackoverflow.com/a/4014407) that the block \n   belongs to the secret name.\n\n5) **ChaCha20** encrypts the block data using the 256-bit private key (2) and \n   96-bit block nonce (3).\n \n6) **CRC-32** checksum verifies the entry data decrypted from the block.\n\n   This verification occurs when we have already beleive (4) that the private \n   key is correct. Therefore, it is really only a self-test to see if the data \n   is decoded as expected.\n\n   This checksum is saved inside the encrypted stream. If the data in two  \n   blocks are the same, it will not be noticeable from the outside due to \n   different nonce (3) values.\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frtmigo%2Fdmk_py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frtmigo%2Fdmk_py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frtmigo%2Fdmk_py/lists"}